Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Workaround for custom UIViewController animations in landscape?

I have a custom animated UIViewController transition, and it seems that there is a bug in iOS that screws up the layout in landscape orientation. In the main animation method, i'm given a mix of landscape and portrait views. (In portrait the views are all portrait, so no problem.)

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
  UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  UIView *containerView = [transitionContext containerView];

  // fromViewController.view => landscape, transform
  // toViewController.view => portrait, transform
  // containerView => portrait, no transform

  [containerView addSubview:toViewController.view];

  // ...animation... //
}

I know that the frame property is not reliable when a view has a transform - so I'm guessing this is the root of the problem. In landscape mode, the to/from viewControllers views have a 90 deg clockwise transform [0 -1 1 0]. I've tried using bounds/center to size and position the view, as well removing the transform and then reapplying it, but UIKit fights me and insists on displaying the view as portrait. Annoying!

In the screenshot, the dark grey is the UIWindow background, and the red is the added modal view controller which should cover the whole screen.

the red view is in portrait

Anyone found a workaround?

like image 555
Jason Moore Avatar asked Nov 16 '13 02:11

Jason Moore


2 Answers

Ok, the fix is surprisingly simple:

Set the toViewController frame to the container before adding the view to the container.

toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];

Update: There is still a limitation in that you don't know the orientation of the frame. It is portrait initially, but stretched into landscape when it is displayed on screen. If you wanted to slide in the view from the right, in landscape it might slide in from the "top" (or the bottom if viewing the other landscape!)

like image 171
Jason Moore Avatar answered Dec 24 '22 23:12

Jason Moore


I came across this issue and I just don't feel that the above solutions do this any justice. I propose a solution that doesn't require hacky code and hard coded frames.

UIView has an awesome function to convert a CGRect into the coordinate space of another (namely; +[UIView convertRect:fromView:]). So I want to detail a far simpler way one can achieve this effect in any orientation without any hardcoded values. In this example lets say we want a simple animation that slides a view in from the right of the screen.

So in our animator's animateTransition(:) we could simply perform the following:

Swift

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    
    let toView = toViewController.view
    let fromView = fromViewController.view
    let containerView = transitionContext.containerView()
    
    if(isPresenting) {
        //now we want to slide in from the right
        let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
        toView.frame = containerView.convertRect(startingRect, fromView:fromView);
        containerView.addSubview(toView)
        let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                toView.frame = destinationRect
        }, completion: { (complete) -> Void in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    } else {
        //we want to slide out to the right
        let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
        UIView.animateWithDuration(transitionDuration(transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.7,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                fromView.frame = endingRect
            }, completion: { (complete) -> Void in
                if !transitionContext.transitionWasCancelled() {
                    fromView.removeFromSuperview()
                }
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    }
}

Objective-C

UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];

if(self.isPresenting) {
    //now we want to slide in from the right
    CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
    toView.frame = [containerView convertRect:startingRect fromView:fromView];
    [containerView addSubview:toView];
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         toView.frame = [containerView convertRect:fromView.bounds
                                                          fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

} else {
    //we want to slide out to the right
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
                         fromView.frame = [containerView convertRect:endingRect fromView:fromView];
                     }
                     completion:^(BOOL finished) {
                         [fromView removeFromSuperview];
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

}

I hope this helps someone else who came here in the same boat (if it does, an up-vote won't hurt :) )

like image 37
Daniel Galasko Avatar answered Dec 25 '22 01:12

Daniel Galasko