自訂presentViewController的轉場動畫(Swift)
前言:
iOS預設的presentViewController
的切換動畫是從底部推入,消失是從頂部推出。但是,因為iOS系統預設的是適配所有轉場內容相關的。而針對特定的轉場上下文,我們能做出更好的效果。
Tips:所謂的轉場上下文,就是轉場的開始View和結束View,以及對應的ViewController
目標效果
最終的效果
準備工作
首先寫出一個CollectionView
,每個Cell是一個圖片,由於本文的核心是如何轉場,所以CollectionView
的部分略過。寫完了之後,是這樣的效果
點擊某一個CollectionView Cell查看大圖,再點擊大圖圖片消失
如何?自訂轉場動畫
iOS 8之後,我們可以通過設定ViewController
的transitioningDelegate
來設定代理來處理轉場動畫。
通過文檔,可以看到transitioningDelegate
是一個實現UIViewControllerTransitioningDelegate
協議的對象,先看看這個協議,本文主要利用以下兩個方法
animationControllerForDismissedController animationControllerForPresentedController
著兩個方法的目的是,提供一個遵循UIViewControllerAnimatedTransitioning
協議的對象,然後又這個對象來實際處理專場。通過名字就可以看出來,一個是處理present一個是處理dismiss。
本文的設計是讓ViewController
來處理轉場,在didSelectItemAtIndexPath
中,設定pvc的轉場代理
dvc.transitioningDelegate = self
然後,寫一個extension來實現協議
extension ViewController:UIViewControllerTransitioningDelegate{ func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil } func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil }}
這時候,再運行項目,會發現沒有任何變化,還是預設的轉場方式。因為我們還沒有提供實際的動畫 。當上述兩個代理方法返回nil的時候,系統會使用預設的方式
實現UIViewControllerAnimatedTransitioning
建立一個檔案,命名為Animator.swift,然後建立一個類,處理Present的轉場(Dismiss類似),實現UIViewControllerAnimatedTransitioning協議
class PresentAnimator: NSObject,UIViewControllerAnimatedTransitioning{}
這時候,會報錯沒有實現協議,然後,我們添加協議方法,這時候的Animator.swift如下
import Foundationimport UIKitclass PresentAnimator: NSObject,UIViewControllerAnimatedTransitioning{ let duration = 0.5 //動畫的時間 var originFrame = CGRectZero //點擊Cell的frame func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return duration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { }}
簡單介紹下這裡的協議方法
transitionDuration,返迴轉場動畫的時間 animateTransition,進行實際的轉場動畫,通過參數transitionContext(轉場上下文)來擷取轉場的fromView,toView,fromViewController,toViewController。轉場的原理
轉場開始的時候,自動把FromView添加到轉場ContainView 轉場結束的時候,自動把FromView移除ContainView
所以,開發人員要做的就是
把toView添加到轉場ContainView中,並且定義好toView的初始位置和狀態 定義好FromView和ToView的轉場結束時候的狀態 建立動畫實現實際的轉場動畫
基於上述的原理,我們修改實際的動畫
func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containView = transitionContext.containerView() let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! let finalFrame = toView.frame let xScale = originFrame.size.width/toView.frame.size.width let yScale = originFrame.size.height/toView.frame.size.height toView.transform = CGAffineTransformMakeScale(xScale, yScale) toView.center = CGPointMake(CGRectGetMidX(originFrame), CGRectGetMidY(originFrame)) containView?.addSubview(toView) UIView.animateWithDuration(duration, animations: { () -> Void in toView.center = CGPointMake(CGRectGetMidX(finalFrame), CGRectGetMidY(finalFrame)) toView.transform = CGAffineTransformIdentity }) { (finished) -> Void in transitionContext.completeTransition(true) } }
然後,在ViewController.swfit中,添加一屬性
let presentAnimator = PresentAnimator()
修改didSelectItemAtIndexPath
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { let dvc = DetailViewController() dvc.image = UIImage(named: "image.jpg") dvc.transitioningDelegate = self let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FullImageCell presentAnimator.originFrame = cell.convertRect(cell.imageview.frame, toView: nil) self.presentViewController(dvc, animated: true, completion: nil) }
修改代理方法
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return presentAnimator }
這時候的動畫效果如Gif
同理,為dismiss添加轉場動畫
在Animator.swift中添加一個新的類
class DismisssAnimator:NSObject,UIViewControllerAnimatedTransitioning{ let duration = 0.6 var originFrame = CGRectZero func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return duration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containView = transitionContext.containerView() let toView = transitionContext.viewForKey(UITransitionContextToViewKey)! //Collection View let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)! //全屏的imageview let xScale = originFrame.size.width/toView.frame.size.width let yScale = originFrame.size.height/toView.frame.size.height containView?.addSubview(toView) containView?.bringSubviewToFront(fromView) UIView.animateWithDuration(duration, animations: { () -> Void in fromView.center = CGPointMake(CGRectGetMidX(self.originFrame), CGRectGetMidY(self.originFrame)) fromView.transform = CGAffineTransformMakeScale(xScale, yScale) }) { (finished) -> Void in transitionContext.completeTransition(true) } }}
然後,在ViewController.swift中didSelectItemAtIndexPath
設定presentAnimator.frame下一行添加
dismissAnimator.originFrame = cell.convertRect(cell.imageview.frame, toView: nil)
其中,dismissAnimator是ViewController的一個屬性
let dismissAnimator = DismisssAnimator()
然後,在代理方法中,返回
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return dismissAnimator; }
最終的效果