添加 UIViewControllerAnimatedTransitioning
添加一個 Cocoa Touch Class,繼承自 NSObject,取名 BWFlipTransionPush(名字嘛,你開心就好。),遵守 UIViewControllerAnimatedTransitioning 協議。
實現協議的兩個方法,並在其中編寫 Push 的動畫。 具體的動畫實現過程都在代碼的注釋裡 :
func animateTransition(transitionContext:UIViewControllerContextTransitioning){
letfromVC=transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)as!FirstViewController
lettoVC=transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)as!SecondViewController
letcontainer=transitionContext.containerView()
container.addSubview(toVC.view)
container.bringSubviewToFront(fromVC.view)
//改變m34
vartransfrom=CATransform3DIdentity
transfrom.m34= -0.002
container.layer.sublayerTransform=transfrom
//設置anrchPoint 和 position
letinitalFrame=transitionContext.initialFrameForViewController(fromVC)
toVC.view.frame=initalFrame
fromVC.view.frame=initalFrame
fromVC.view.layer.anchorPoint=CGPointMake(0,0.5)
fromVC.view.layer.position=CGPointMake(0,initalFrame.height/2.0)
//添加陰影效果
letshadowLayer=CAGradientLayer()
shadowLayer.colors=[UIColor(white:0,alpha:1).CGColor,UIColor(white:0,alpha:0.5).CGColor,UIColor(white:1,alpha:0.5)]
shadowLayer.startPoint=CGPointMake(0,0.5)
shadowLayer.endPoint=CGPointMake(1,0.5)
shadowLayer.frame=initalFrame
letshadow=UIView(frame:initalFrame)
shadow.backgroundColor=UIColor.clearColor()
shadow.layer.addSublayer(shadowLayer)
fromVC.view.addSubview(shadow)
shadow.alpha=0
//動畫
UIView.animateWithDuration(transitionDuration(transitionContext),delay:0,options:UIViewAnimationOptions.CurveEaseOut,animations:{()->Voidin
fromVC.view.layer.transform=CATransform3DMakeRotation(CGFloat(-M_PI_2),0,1,0)
shadow.alpha=1.0
}){(finished:Bool)->Voidin
fromVC.view.layer.anchorPoint=CGPointMake(0.5,0.5)
fromVC.view.layer.position=CGPointMake(CGRectGetMidX(initalFrame),CGRectGetMidY(initalFrame))
fromVC.view.layer.transform=CATransform3DIdentity
shadow.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
動畫的過程我就不多說了,仔細看就會明白。
使用動畫
讓 FirstViewController 遵守 UIViewControllerTransitioningDelegate 協議,並將 self.transitioningDelegate 設置為 self。
實現 UIViewControllerTransitioningDelegate 協議的兩個方法,用來指定動畫類。
//動畫Push
func animationControllerForPresentedController(presented:UIViewController,presentingControllerpresenting:UIViewController,sourceControllersource:UIViewController)->UIViewControllerAnimatedTransitioning?{
returnBWFlipTransionPush()
}
//動畫Pop
func animationControllerForDismissedController(dismissed:UIViewController)->UIViewControllerAnimatedTransitioning?{
returnBWFlipTransionPop()
}
OK,如果你完成了Pop動畫,那麼現在就可以實現自定義 Modal 轉場了。現在只差手勢驅動了。
手勢驅動
想要同時實現 Push 和 Pop 手勢,就需要給兩個 viewController.view 添加手勢。首先在 FirstViewController 中給自己添加一個屏幕右邊的手勢,在 prepareForSegue() 方法中給 SecondViewController.view 添加一個屏幕左邊的手勢,讓它們使用同一個手勢監聽方法。
實現監聽方法,不多說,和之前一樣,但還是有仔細看,因為本示例中轉場動畫比較特殊,而且有兩個手勢,所以這裡計算百分比使用的是 KeyWindow。同時不要忘了:UIPercentDrivenInteractiveTransition屬性。
func edgePanGesture(edgePan:UIScreenEdgePanGestureRecognizer){
letprogress=abs(edgePan.translationInView(UIApplication.sharedApplication().keyWindow!).x)/UIApplication.sharedApplication().keyWindow!.bounds.width
ifedgePan.state==UIGestureRecognizerState.Began{
self.percentDrivenTransition=UIPercentDrivenInteractiveTransition()
ifedgePan.edges==UIRectEdge.Right{
self.performSegueWithIdentifier("present",sender:nil)
}elseifedgePan.edges==UIRectEdge.Left{
self.dismissViewControllerAnimated(true,completion:nil)
}
}elseifedgePan.state==UIGestureRecognizerState.Changed{
self.percentDrivenTransition?.updateInteractiveTransition(progress)
}elseifedgePan.state==UIGestureRecognizerState.Cancelled||edgePan.state==UIGestureRecognizerState.Ended{
ifprogress>0.5{
self.percentDrivenTransition?.finishInteractiveTransition()
}else{
self.percentDrivenTransition?.cancelInteractiveTransition()
}
self.percentDrivenTransition=nil
}
}
實現 UIViewControllerTransitioningDelegate 協議的另外兩個方法,分別返回 Present 和 Dismiss 動畫的百分比。
//百分比Push
func interactionControllerForPresentation(animator:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{
returnself.percentDrivenTransition
}
//百分比Pop
func interactionControllerForDismissal(animator:UIViewControllerAnimatedTransitioning)->UIViewControllerInteractiveTransitioning?{
returnself.percentDrivenTransition
}
現在,基於 Modal 的自定義轉場動畫示例就完成了。獲取完整源代碼:FlipTransion
Segue
這種方法比較特殊,是將 Stroyboard 中的拖線與自定義的 UIStoryboardSegue 類綁定自實現定義轉場過程動畫。
首先我們來看看 UIStoryboardSegue 是什麼樣的。
@availability(iOS,introduced=5.0)
classUIStoryboardSegue:NSObject{
// Convenience constructor for returning a segue that performs a handler block in its -perform method.
@availability(iOS,introduced=6.0)
convenience init(identifier:String?,source:UIViewController,destination:UIViewController,performHandler:()->Void)
init!(identifier:String?,source:UIViewController,destination:UIViewController)// Designated initializer
varidentifier:String?{get}
varsourceViewController:AnyObject{get}
vardestinationViewController:AnyObject{get}
func perform()
}
以上是 UIStoryboardSegue 類的定義。從中可以看出,只有一個方法 perform(),所以很明顯,就是重寫這個方法來自定義轉場動畫。
再注意它的其他屬性:sourceViewController 和 destinationViewController,通過這兩個屬性,我們就可以訪問一個轉場動畫中的兩個主角了,於是自定義動畫就可以隨心所欲了。
只有一點需要注意:在拖線的時候,注意在彈出的選項中選擇 custom。然後就可以和自定義的 UIStoryboardSegue 綁定了。
那麼,問題來了,這裡只有 perform,那 返回時的動畫怎麼辦呢?請往下看:
Dismiss
由於 perfrom 的方法叫做:segue,那麼返回轉場的上一個控制器叫做: unwind segue
解除轉場(unwind segue)通常和正常自定義轉場(segue)一起出現。
要解除轉場起作用,我們必須重寫perform方法,並應用自定義動畫。另外,導航返回源視圖控制器的過渡效果不需要和對應的正常轉場相同。
其實現步驟 為:
創建一個 IBAction 方法,該方法在解除轉場被執行的時候會選擇地執行一些代碼。這個方法可以有你想要的任何名字,而且不強制包含其它東西。它需要定義,但可以留空,解除轉場的定義需要依賴這個方法。
解除轉場的創建,設置的配置。這和之前的轉場創建不太一樣,等下我們將看看這個是怎麼實現的。
通過重寫 UIStoryboardSegue 子類裡的 perform() 方法,來實現自定義動畫。
UIViewController類 提供了特定方法的定義,所以系統知道解除轉場即將執行。
當然,這麼說有一些讓人琢磨不透,不知道什麼意思。那麼,下面再通過一個示例來深入了解一下。
Segue 示例
這個示例是我自己寫的,源代碼地址:SegueTransion,開門見山,直接上圖。
創建兩個 UIViewController, 分別命名為:FirstViewController 和 SecondViewController。並在 Storyboard 中添加兩個 UIViewController 並綁定。
分別給兩個控制器添加背景圖片或使用不同的背景色,用以區分。在 FirstViewController 中添加一個觸發按鈕,並拖線到 SecondViewController 中,在彈出的選項中選擇 custion。
添加一個 Cocoa Touch Class,繼承自 UIStoryboardSegue,取名 FirstSegue(名字請隨意)。並將其綁定到上一步中拖拽的 segue 上。
重寫 FirstSegue 中的 perform() 方法,在其中編寫動畫邏輯。
override func perform(){
varfirstVCView=self.sourceViewController.viewasUIView!
varsecondVCView=self.destinationViewController.viewasUIView!
letscreenWidth=UIScreen.mainScreen().bounds.size.width
letscreenHeight=UIScreen.mainScreen().bounds.size.height
secondVCView.frame=CGRectMake(0.0,screenHeight,screenWidth,screenHeight)
letwindow=UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView,aboveSubview:firstVCView)
UIView.animateWithDuration(0.5,delay:0,usingSpringWithDamping:0.5,initialSpringVelocity:0,options:UIViewAnimationOptions.CurveLinear,animations:{()->Voidin
secondVCView.frame=CGRectOffset(secondVCView.frame,0.0,-screenHeight)
}){(finished:Bool)->Voidin
self.sourceViewController.presentViewController(self.destinationViewControlleras!UIViewController,
animated:false,
completion:nil)
}
}
還是一樣,動畫的過程自己看,都是很簡單的。
Present手勢
這裡需要注意,使用這種方式自定義的轉場動畫不能動態手勢驅動,也就是說不能根據手勢百分比動態改變動畫完成度。
所以,這裡只是簡單的添加一個滑動手勢(swip)。
在 FisrtViewController 中添加手勢:
varswipeGestureRecognizer:UISwipeGestureRecognizer=UISwipeGestureRecognizer(target:self,action:"showSecondViewController")
swipeGestureRecognizer.direction=UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeGestureRecognizer)
實現手勢監聽方法:
func showSecondViewController(){
self.performSegueWithIdentifier("idFirstSegue",sender:self)
}
現在已經可以 present 了,接下來實現 dismiss。
Dismiss
在 FirstViewController 中添加一個 IBAction 方法,方法名可以隨便,有沒有返回值都隨便。
在 Storyboard 中選擇 SecondViewController 按住 control鍵 拖線到 SecondViewController 的 Exit 圖標。並在彈出選項中選擇上一步添加 IBAction 的方法。
在 Storyboard 左側的文檔視圖中找到上一步拖的 segue,並設置 identifier
再添加一個 Cocoa Touch Class,繼承自 UIStoryboardSegue,取名 FirstSegueUnWind(名字請隨意)。並重寫其 perform() 方法,用來實現 dismiss 動畫。
在 FirstViewController 中重寫下面方法。並根據 identifier 判斷是不是需要 dismiss,如果是就返回剛剛創建的 FirstUnWindSegue。
override func segueForUnwindingToViewController(toViewController:UIViewController,fromViewController:UIViewController,identifier:String?)->UIStoryboardSegue{
ifidentifier=="firstSegueUnwind"{
returnFirstUnwindSegue(identifier:identifier,source:fromViewController,destination:toViewController,performHandler:{()->Voidin
})
}
returnsuper.segueForUnwindingToViewController(toViewController,fromViewController:fromViewController,identifier:identifier)
}