UIViewController轉場動畫,uiviewcontroller
UIViewControllerAnimatedTransitioning
在UINavigationController中如何使用UIViewControllerAnimatedTransitioning?
參考:How to use UIViewControllerAnimatedTransitioning with UINavigationController?
1.動畫的FromViewController要遵循UINavigationControllerDelegate協議。
2.FromViewController中,在如下的代理方法中返回自訂的轉場動畫對象:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { TransitionAnimator *animator = [TransitionAnimator new]; animator.presenting = (operation == UINavigationControllerOperationPush); return animator; }
3.建立自訂的動畫類,需遵循UIViewControllerContextTransitioning協議,並實現如下的代理方法:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { //返回動畫執行的時間 return 0.5f; }- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {// Grab the from and to view controllers from the contextUIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];// Set our ending frame. We'll modify this later if we have toCGRect endFrame = CGRectMake(80, 280, 160, 100);if (self.presenting) { fromViewController.view.userInteractionEnabled = NO; [transitionContext.containerView addSubview:fromViewController.view]; [transitionContext.containerView addSubview:toViewController.view]; CGRect startFrame = endFrame; startFrame.origin.x += 320; toViewController.view.frame = startFrame; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed; toViewController.view.frame = endFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }];}else { toViewController.view.userInteractionEnabled = YES; [transitionContext.containerView addSubview:toViewController.view]; [transitionContext.containerView addSubview:fromViewController.view]; endFrame.origin.x += 320; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic; fromViewController.view.frame = endFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:YES]; }];}}
關於UIViewControllerAnimatedTransitioning的資料可參考:
- 輕鬆學習之二——iOS利用Runtime自訂控制器POP手勢動畫
- iOS 7 新特性:視圖控制器切換API
- ViewController的進階轉場動畫
- View Controller 轉場
以下內容來自
Introduction to Custom View Controller Transitions and Animations
建立自訂的transition,按如下的3步:
- Create a class that implements the UIViewControllerAnimatedTransitioning protocol. Here you will write code that performs the animation. This class is referred to as the animation controller.
- Before presenting a view controller, set a class as its transitioning delegate. The delegate will get a callback for the animation controller to be used when presenting the view controller.
- Implement the callback method to return an instance of the animation controller from the first step.
自訂Present過渡(Custom Present Transition)
首先建立一個animation controller。如下,建立一個CustomPresentAnimationController 類繼承自NSObject
class CustomPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
UIViewControllerAnimatedTransitioning協議有兩個必須實現的方法。
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 2.5}func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController) let containerView = transitionContext.containerView() let bounds = UIScreen.mainScreen().bounds toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height) containerView.addSubview(toViewController.view) UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .CurveLinear, animations: { fromViewController.view.alpha = 0.5 toViewController.view.frame = finalFrameForVC }, completion: { finished in transitionContext.completeTransition(true) fromViewController.view.alpha = 1.0 })}
當前的控制器要遵循UIViewControllerTransitioningDelegate協議
class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate {
UIViewController有一個transitionDelegate屬性,支援自訂的transitions。當一個View controller 過渡到另一個View controller的時候,系統會檢查這個屬性來決定是否使用自訂的transition。由UIViewControllerTransitioningDelegate來提供這個自訂的transition。
let customPresentAnimationController = CustomPresentAnimationController()override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showAction" { let toViewController = segue.destinationViewController as UIViewController toViewController.transitioningDelegate = self }}func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return customPresentAnimationController}
如果想要一個不同的效果,可以把CustomPresentAnimationController.swift中的代碼:
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
改為如下的形式,把View controller的frame的原點位置改在螢幕的上面:
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, -bounds.size.height)
自訂Dismiss過渡(Custom Dismiss Transition)
UIViewControllerTransitioningDelegate 也允許指定一個animation controller來dismiss一個view controller。
建立一個CustomDismissAnimationController類,繼承自NSObject。如下:
class CustomDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
在這個類中添加如下的代碼:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 2}func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController) let containerView = transitionContext.containerView() toViewController.view.frame = finalFrameForVC toViewController.view.alpha = 0.5 containerView.addSubview(toViewController.view) containerView.sendSubviewToBack(toViewController.view) UIView.animateWithDuration(transitionDuration(transitionContext), animations: { fromViewController.view.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2) toViewController.view.alpha = 1.0 }, completion: { finished in transitionContext.completeTransition(true) })}
在ItemsTableViewController中加入如下的屬性:
let customDismissAnimationController = CustomDismissAnimationController()
並在類中添加如下的方法:
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return customDismissAnimationController}
UIViewControllerTransitioningDelegate 協議提供了如上的方法,這個方法用來擷取dismiss view controller的animation controller。
啟動並執行效果如下:
動畫效果並不是我們想要的。原因是改變view的frame並不會對其子view有影響。可以使用UIView的snapshotting來修改它。
UIView的snapshotting是UIView的截屏,使其成為輕量級的UIView。我們將會在動畫過程中使用這個截屏,而不是真正的view。
把animateTransition()函數替換成如下形式:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController) let containerView = transitionContext.containerView() toViewController.view.frame = finalFrameForVC toViewController.view.alpha = 0.5 containerView.addSubview(toViewController.view) containerView.sendSubviewToBack(toViewController.view) let snapshotView = fromViewController.view.snapshotViewAfterScreenUpdates(false) snapshotView.frame = fromViewController.view.frame containerView.addSubview(snapshotView) fromViewController.view.removeFromSuperview() UIView.animateWithDuration(transitionDuration(transitionContext), animations: { snapshotView.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2) toViewController.view.alpha = 1.0 }, completion: { finished in snapshotView.removeFromSuperview() transitionContext.completeTransition(true) }) }
導航控制器過渡(Navigation controller transitions)
自訂CustomNavigationAnimationController類繼承自NSObject,如下:
class CustomNavigationAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
把這個類中添加如下的內容。給這個animation controller使用一個簡單的立方體動畫。使用了一個reverse變數,用來決定動畫的方向。
var reverse: Bool = falsefunc transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 1.5}func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView() let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toView = toViewController.view let fromView = fromViewController.view let direction: CGFloat = reverse ? -1 : 1 let const: CGFloat = -0.005 toView.layer.anchorPoint = CGPointMake(direction == 1 ? 0 : 1, 0.5) fromView.layer.anchorPoint = CGPointMake(direction == 1 ? 1 : 0, 0.5) var viewFromTransform: CATransform3D = CATransform3DMakeRotation(direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0) var viewToTransform: CATransform3D = CATransform3DMakeRotation(-direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0) viewFromTransform.m34 = const viewToTransform.m34 = const containerView.transform = CGAffineTransformMakeTranslation(direction * containerView.frame.size.width / 2.0, 0) toView.layer.transform = viewToTransform containerView.addSubview(toView) UIView.animateWithDuration(transitionDuration(transitionContext), animations: { containerView.transform = CGAffineTransformMakeTranslation(-direction * containerView.frame.size.width / 2.0, 0) fromView.layer.transform = viewFromTransform toView.layer.transform = CATransform3DIdentity }, completion: { finished in containerView.transform = CGAffineTransformIdentity fromView.layer.transform = CATransform3DIdentity toView.layer.transform = CATransform3DIdentity fromView.layer.anchorPoint = CGPointMake(0.5, 0.5) toView.layer.anchorPoint = CGPointMake(0.5, 0.5) if (transitionContext.transitionWasCancelled()) { toView.removeFromSuperview() } else { fromView.removeFromSuperview() } transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) }) }
開啟ItemsTableViewController.swift,修改類的聲明如下:
class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
由UINavigationControllerDelegate 來提供animation controller。
添加如下的屬性到類中。
let customNavigationAnimationController = CustomNavigationAnimationController()
在viewDidLoad()中添加如下的代碼:
navigationController?.delegate = self
在類中添加如下的方法:
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { customNavigationAnimationController.reverse = operation == .Pop return customNavigationAnimationController}
Making it Interactive
iOS內建的app有這個特徵
我們需要一個interaction controller。interaction controller使用UIViewControllerInteractiveTransitioning協議。navigation controller delegate或者transitioning delegate,在requesting一個animation controller之後還會requests 一個可選的 interaction controller。
來建立interaction controller。建立一個新的類叫做CustomInteractionController,繼承自UIPercentDrivenInteractiveTransition。
UIPercentDrivenInteractiveTransition實現了UIViewControllerInteractiveTransitioning協議。
要想使用UIPercentDrivenInteractiveTransition,你的animation controller必須有一個UIView動畫,這樣這個動畫可以被停止、恢複和播放。
在這個類中加入如下的代碼:
var navigationController: UINavigationController!var shouldCompleteTransition = falsevar transitionInProgress = falsevar completionSeed: CGFloat { return 1 - percentComplete}func attachToViewController(viewController: UIViewController) { navigationController = viewController.navigationController setupGestureRecognizer(viewController.view)}private func setupGestureRecognizer(view: UIView) { view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "handlePanGesture:"))}func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) { let viewTranslation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!) switch gestureRecognizer.state { case .Began: transitionInProgress = true navigationController.popViewControllerAnimated(true) case .Changed: var const = CGFloat(fminf(fmaxf(Float(viewTranslation.x / 200.0), 0.0), 1.0)) shouldCompleteTransition = const > 0.5 updateInteractiveTransition(const) case .Cancelled, .Ended: transitionInProgress = false if !shouldCompleteTransition || gestureRecognizer.state == .Cancelled { cancelInteractiveTransition() } else { finishInteractiveTransition() } default: println("Swift switch must be exhaustive, thus the default") }}
使用interaction controller,在ItemsTableViewController.swift,加入如下的屬性:
let customInteractionController = CustomInteractionController()
在navigationController(_:animationControllerForOperation:
fromViewController:toViewController:)方法的開始位置,加入如下的代碼:
if operation == .Push { customInteractionController.attachToViewController(toVC)}
然後再加入如下的方法:
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return customInteractionController.transitionInProgress ? customInteractionController : nil}
download the completed project here
其它參考文章:
- How To Make A View Controller Transition Animation Like in the Ping App
- CREATING A CUSTOM FLIP VIEW CONTROLLER TRANSITION
- INTERACTIVE TRANSITIONS
- iOS7 interactive transitions
- Custom UIViewController Transitions
另推薦一個開源工具,可以很好的在項目中實現滑動返回:
FDFullscreenPopGesture