Custom UIViewController Transitions
Ján Ilavský - @split82
iPhone OS 3.0
iOS 7
Modal View Controller
- (void)presentViewController:animated:completion: !
- (void)dismissViewControllerAnimated:completion:
UIModalTransitionStyle modalTransitionStyle
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) { UIModalTransitionStyleCoverVertical = 0, UIModalTransitionStyleFlipHorizontal, UIModalTransitionStyleCrossDissolve, #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 UIModalTransitionStylePartialCurl, #endif };
UIModalPresentationStyle modalPresentationStyle
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) { UIModalPresentationFullScreen = 0, #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 UIModalPresentationPageSheet, UIModalPresentationFormSheet, UIModalPresentationCurrentContext, #endif #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 UIModalPresentationCustom, UIModalPresentationNone = -1, #endif };
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) { UIModalPresentationFullScreen = 0, #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 UIModalPresentationPageSheet, UIModalPresentationFormSheet, UIModalPresentationCurrentContext, #endif #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 UIModalPresentationCustom, UIModalPresentationNone = -1, #endif };
Custom Fullscreen Non-interactive Transitions
Demo
TestViewController *viewController = [TestViewController new]; !viewController.transitioningDelegate = ??? ![self presentViewController:viewController animated:YES completion:nil];
TestViewController *viewController = [TestViewController new]; !viewController.transitioningDelegate = ??? ![self presentViewController:viewController animated:YES completion:nil];
@protocol UIViewControllerTransitioningDelegate <NSObject> !@optional !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; !!@end
@protocol UIViewControllerTransitioningDelegate <NSObject> !@optional !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; !!@end
@protocol UIViewControllerTransitioningDelegate <NSObject> !@optional !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; !!@end
UIViewController UIViewController
id <UIViewControllerTransitioningDelegate>
transitioningDelegate
animationControllerForPresentedController… animationControllerForDismissedController
id <UIViewControllerAnimatedTransitioning>
id <UIViewControllerAnimatedTransitioning>
present
dismiss
@protocol UIViewControllerAnimatedTransitioning <NSObject> !!- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext; !- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; !!@optional !- (void)animationEnded:(BOOL)transitionCompleted; !@end
@protocol UIViewControllerAnimatedTransitioning <NSObject> !!- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext; !- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; !!@optional !- (void)animationEnded:(BOOL)transitionCompleted; !@end
UIView UIView
UIViewController UIViewController
@protocol UIViewControllerAnimatedTransitioning <NSObject> !!- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext; !- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; !!@optional !- (void)animationEnded:(BOOL)transitionCompleted; !@end
@protocol UIViewControllerAnimatedTransitioning <NSObject> !!- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext; !- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; !!@optional !- (void)animationEnded:(BOOL)transitionCompleted; !@end
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
!Container View
UIView UIView
UIViewController UIViewController
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
initialFrameForViewController finalFrameForViewController
from CGRectZero
to CGRectZero
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; !UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; !!toViewController.view.alpha = 0.0f; toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; ![transitionContext.containerView addSubview:toViewController.view]; [UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { //[fromViewController.view removeFromSuperview]; [transitionContext completeTransition:YES]; }];
Custom Non-Fullscreen Non-interactive Transitions
TestViewController *viewController = [TestViewController new]; !viewController.modalPresentationStyle = UIModalPresentationCustom; viewController.transitioningDelegate = ??? ![self presentViewController:viewController animated:YES completion:nil];
UIView
UIView
Demo
Presentation != Dismissal
Presentation!
Container View
UIView UIView
UIViewController UIViewController
Dismission!
Container View
UIView UIView
UIViewController UIViewController
initialFrameForViewController finalFrameForViewController
from
to CGRectZero CGRectZero
Presentation
initialFrameForViewController finalFrameForViewController
from CGRectZero
to CGRectZero
Dismissal
viewWillDissapear !
viewDidDissapear
FromViewControllerPresentation
viewWillAppear !
viewDidAppear
ToViewControllerDismissal
[transitionContext.containerView addSubview:toViewController.view]; !toViewController.view.frame = CGRectInset([transitionContext initialFrameForViewController:fromViewController], 32.0f, 32.0f); !toViewController.view.alpha = 0.0f; ![UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }];
Presentation
[UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ fromViewController.view.alpha = 0.0f; } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }];
Dismissal
Interactive Transitions
start complete
start complete
cancel
finish
updating
Demo
UIViewController UIViewController
id <UIViewControllerTransitioningDelegate>
transitioningDelegate
animationControllerForPresentedController… animationControllerForDismissedController
id <UIViewControllerAnimatedTransitioning>
id <UIViewControllerAnimatedTransitioning>
present
dismiss
@protocol UIViewControllerTransitioningDelegate <NSObject> !@optional !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; !!@end
@protocol UIViewControllerTransitioningDelegate <NSObject> !@optional !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; !!- (id <UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; !!- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator; !!@end
UIViewController UIViewController
id <UIViewControllerTransitioningDelegate>
transitioningDelegate
animationControllerForPresentedController… animationControllerForDismissedController
id <UIViewControllerAnimatedTransitioning>
id <UIViewControllerAnimatedTransitioning>
present
dismiss
UIViewController UIViewController
id <UIViewControllerTransitioningDelegate>
transitioningDelegate
id <UIViewControllerAnimatedTransitioning>
id <UIViewControllerAnimatedTransitioning>
present
dismiss
id <UIViewControllerInteractiveTra
nsitioning>
id <UIViewControllerInteractiveTra
nsitioning>
id <UIViewControllerInteractiveTransitioning>
@protocol UIViewControllerInteractiveTransitioning <NSObject> !- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext; !@optional !- (CGFloat)completionSpeed; - (UIViewAnimationCurve)completionCurve; !@end
UIPercentDrivenInteractiveTransition
UIPercentDrivenInteractiveTransition
- (void)updateInteractiveTransition:(CGFloat)percentComplete; - (void)cancelInteractiveTransition; - (void)finishInteractiveTransition;
CGFloat scale = [gestureRecognizer scale]; switch ([gestureRecognizer state]) { case UIGestureRecognizerStateBegan: transitionController.interactive = YES; _startScale = scale; [testViewController dismissViewControllerAnimated:YES completion:nil]; } break; case UIGestureRecognizerStateChanged: { CGFloat percent = (1.0 - scale/_startScale); [transitionController.percentDrivenInteractiveTransition updateInteractiveTransition: (percent <= 0.0) ? 0.0 : percent]; break; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: if ([gestureRecognizer velocity] >= 0.0 || [gestureRecognizer state] == UIGestureRecognizerStateCancelled) { [transitionController.percentDrivenInteractiveTransition cancelInteractiveTransition]; } else { [transitionController.percentDrivenInteractiveTransition finishInteractiveTransition]; } break; default: break; }
CGFloat scale = [gestureRecognizer scale]; switch ([gestureRecognizer state]) { case UIGestureRecognizerStateBegan: transitionController.interactive = YES; _startScale = scale; [testViewController dismissViewControllerAnimated:YES completion:nil]; } break; case UIGestureRecognizerStateChanged: { CGFloat percent = (1.0 - scale/_startScale); [transitionController.percentDrivenInteractiveTransition updateInteractiveTransition: (percent <= 0.0) ? 0.0 : percent]; break; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: if ([gestureRecognizer velocity] >= 0.0 || [gestureRecognizer state] == UIGestureRecognizerStateCancelled) { [transitionController.percentDrivenInteractiveTransition cancelInteractiveTransition]; } else { [transitionController.percentDrivenInteractiveTransition finishInteractiveTransition]; } break; default: break; }
Core Animation!!
+[UIView transitionFromView:toView:duration:options:completion:]!!
Custom Animations!!
UIView block-based animations
UIPercentDrivenInteractiveTransition
[UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview];
[transitionContext completeTransition:YES]; }];
[UIView animateWithDuration:self.duration delay:0.0 options:0 animations:^{ toViewController.view.alpha = 1.0f; } completion:^(BOOL finished) { [fromViewController.view removeFromSuperview]; [transitionContext completeTransition: !transitionContext.transitionWasCancelled]; }];
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext { ! _transitionContext = transitionContext; _toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; _toViewController.view.alpha = 0.0f; _toViewController.view.frame = [transitionContext finalFrameForViewController:_toViewController]; [transitionContext.containerView addSubview:_toViewController.view]; }
id <UIViewControllerInteractiveTransitioning>
id <UIViewControllerInteractiveTransitioning>
- (void)updateInteractiveTransition:(CGFloat)percentComplete { _toViewController.view.alpha = percentComplete; [_transitionContext updateInteractiveTransition:percentComplete]; }
id <UIViewControllerInteractiveTransitioning>
- (void)cancelInteractiveTransition { [_transitionContext cancelInteractiveTransition]; [UIView animateWithDuration:0.3 delay:0.0 options:0 animations:^{ _toViewController.view.alpha = 0.0f; } completion:^(BOOL finished) { [_transitionContext completeTransition:!_transitionContext.transitionWasCancelled]; }]; }
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear
viewWillAppear viewWillDisappear
viewDidDisappear
- (void)viewWillAppear:(BOOL)animated { [self doSomeSideEffectsAssumingViewDidAppearIsGoingToBeCalled]; id <UIViewControllerTransitionCoordinator> coordinator; coordinator = [self transitionCoordinator]; if(coordinator && [coordinator initiallyInteractive]) { [transitionCoordinator notifyWhenInteractionEndsUsingBlock: ^(id <UIViewControllerTransitionCoordinatorContext> ctx) { if(ctx.isCancelled) { [self undoSideEffects]; } }]; } }
UIViewControllerTransitionCoordinator
UINavigationController
– pushViewController:animated: – popViewControllerAnimated: – popToRootViewControllerAnimated: – popToViewController:animated:
- (id<UIViewControllerAnimatedTransitioning>)!navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC !toViewController:(UIViewController *)toVC
UINavigationControllerDelegate
- (id<UIViewControllerInteractiveTransitioning>)!navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
UINavigationController
@property(nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer
UITabBarController
@property(nonatomic, assign) UIViewController *selectedViewController @property(nonatomic) NSUInteger selectedIndex
UITabBarControllerDelegate
- (id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController; !- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC;
finty fň
// Snapshot UIView *fromView = [fromViewController.view snapshotViewAfterScreenUpdates:NO]; !// Interactivity fromViewController.view.userInteractionEnabled = NO; toViewController.view.userInteractionEnabled = YES; transitionContext.containerView.userInteractionEnabled = YES; ![transitionContext.containerView addSubview:fromView]; [transitionContext.containerView addSubview:toViewController.view]; !// Finish before animation [transitionContext completeTransition:YES]; ![UIView animateWithDuration: . . .
// Prepare BitmapContext CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); GLubyte *textureData = malloc(textureWidth * textureHeight * 4); memset_pattern4(textureData, "\0\0\0\0", textureWidth * textureHeight * 4); NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * textureWidth; NSUInteger bitsPerComponent = 8; CGContextRef bitmapContext = CGBitmapContextCreate(textureData, textureWidth, textureHeight, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(colorSpace); // draw [view.layer renderInContext:bitmapContext]; CGContextRelease(bitmapContext); !// set data for texture glBindTexture(GL_TEXTURE_2D, texture); // set bitmap data into texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData); // Don't need this data anymore free(textureData);
fuck off view controllers
Custom UIViewController Transitions
Ján Ilavský - @split82