匿名社交應用Secret的開發者開發了一款叫作Ping的應用,用戶能夠他們感興趣的話題的推送。app
Ping有一個很炫的東西,就是主界面和之間切換的動畫作的很是的好。每次看到一個很是炫的動畫,都不禁得會想:「這個東西我要不要本身實現如下」。哈哈~~~ide
這個教程裏,你會學到如何用Swift實現這樣的很酷的動畫。你會學到如何使用shape layer,遮罩和使用UIViewControllerAnimnatedTransitioning協議和UIPercentDrivenInteractivetransition類等實現View Controller界面切換動畫。動畫
不過須要注意,這裏假定你已經有必定的Swift開發基礎。若是隻是初學的話,請自行查看我得其餘Swift教程。spa
開篇簡介代理
咱們主要介紹Ping裏從一個View Controller跳轉到另外一個的時候的動畫。code
在iOS裏,你能夠在UINavigationController中放入兩個View Controller,並實現UIViewControllerAnimatedTransitioning協議來實現界面切換的動畫。具體的細節有:orm
這些動畫,你能夠用UIView得動畫方法來做,也能夠用core animation這樣的比較底層的方法來作。本教程會使用後者。對象
如何實現blog
如今你已經知道代碼大概會添加到什麼地方。下面討論下如何實現那個Ping的那個圈圈動畫。這動畫嚴格的描述起來是:教程
你能夠用CALayer的mask能夠達到這個效果。固然還須要設置alpha爲0來隱藏下面一個視圖的內容。alpha值設定爲1的時候顯示下面視圖的內容。
如今你就懂了遮罩了。下一步就是決定用哪種CAShapeLayer來實現這個遮罩。只須要修改這些CAShapeLayer組成的圓圈的半徑。
如今開始
這裏就不十分詳細的敘述了,都是些關於建立和配置項目的步驟。
1. 建立一個新的項目。選擇一個single view application
2. 項目名稱設置爲CircleTransition。語言選擇Swift。Devices就選擇iPhone
項目到此初步建立好了。在Main.stroyboard裏只有一個view controller。可是咱們的動畫須要兩個至少的view controller。不過首先須要把如今的這個view controller和UINavigationController關聯起來。選中這個惟一的view controller,以後在菜單欄中選擇Editor->Embed In->Navigation Controller。以後這個navigation controller就會成爲initial controller,後面連着最開始生成的那個view controller。以後,選中這個navigation controller,在右側菜單欄的第四個tab中勾去「Shows navigation bar」。由於在咱們的app中不須要navigation bar。
接下來添加另一個view controller。給這個view controller指定class爲ViewController。
而後,給每個view controller,除了navigation controller,添加一個按鈕。雙擊按鈕,刪除文字,以後把按鈕的背景色設置爲黑色。另一個按鈕也一樣處理。給這兩個按鈕設定autolayout。指定他們在右上角上。指定這兩個按鈕的寬度和高度爲40。
最後讓按鈕變成圓形的。右邊菜單的第三個tab中選擇「user defined runtime attributes」。點下面的加號,添加如圖所示的內容。設置button的corner radius爲15。
這樣這個按鈕在運行起來的時候就是圓形的了。設定完成以後暫時看不到這個效果。運行起來之後:
如今須要在每一個view controller中添加些內容了。先把這兩個view controller的背景色修改一下。
如今這個app大體已經成型了。不一樣的顏色能夠表明你未來要顯示出來的各類各樣的內容。所須要的就是把這個兩個view controller連起來。在橘色的controller的按鈕中放下鼠標。按下ctrl而後把光標拖動到另一個controller上。這是會出現一個彈出的菜單。把這個菜單的action用一樣的方法和這個controller再鏈接一次,並選擇show。這樣,在這個按鈕選擇的時候,navigation controller就會push到下一個view controller中。這是一個segue。後面的教程會須要這個segue因此這裏給這個segue一個identifer,叫作「PushSegue」。運行代碼,點擊橘色controller的按鈕就會跳轉到紫色的controller了。
由於這是一個循環的過程,因此從橘色到紫色以後還須要從紫色回到橘色。如今就完成這個功能。首先,在紫色controller綁定的ViewController類中添加一個action方法。
@IBAction func circleTapped(sender: UIButton){ self.navigationController?.popViewControllerAnimated(true) }
並添加紫色controller上的按鈕的引用,這個會在後面用到:
@IBOutlet weak var button: UIButton!
以後給紫色controller的按鈕的「touch up inside」事件添加上面的@IBAction。
綁定按鈕的屬性:
再運行起來看看。橘色到紫色,紫色到橘色循環往復!
注意:兩個view controller都須要綁定按鈕和按鈕事件!不然後面的動畫只能執行一次!
自定義動畫
這裏主要處理的就是navigation controller的push和pop動畫。這須要咱們實現UINavigationControllerDelegate協議的animationControllerForOperation方法。直接在ViewController中添加一個新的類:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate{ func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil } }
首先,在右側的菜單中選中Object這個item。
以後,把這個東西拖動到navigation controller secene下。
而後選中這個Object,在右側菜單的第三個tab上修改class爲咱們剛剛定義的NavigationControllerDelegate。
下一步,給navigation controller指定delegate。選中navigation controller,而後在右側最後的菜單中鏈接navigation controller的delegate選項到剛剛拖進來的Object上:
這個時候仍是不會有特定的效果出現。由於方法仍是空的,只能算是一個placeholder方法。
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil }
這個方法接受兩個在navigation controller中得controller。從一個跳轉到另外一個的兩個controller。並返回一個實現了UIViewControllerAnimatedTransitioning的對象。因此,咱們須要建立一個實現了UIViewControllerAnimatedTransitioning協議的類。
class CircleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning
首先添加一個屬性:
weak var transitionContext: UIViewControllerContextTransitioning?
這個屬性會在後面的代碼中用到。
添加一個方法:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 0.5 }
這個方法返回動畫執行的時間。
添加動畫方法:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // 1 self.transitionContext = transitionContext // 2 var containerView = transitionContext.containerView() var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as ViewController var toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as ViewController var button = fromViewController.button // 3 containerView.addSubview(toViewController.view) // 4 var circleMaskPathInitial = UIBezierPath(ovalInRect: button.frame) var extremePoint = CGPointMake(button.center.x, button.center.y - CGRectGetHeight(toViewController.view.bounds)) // need more research var radius = sqrt(extremePoint.x * extremePoint.x + extremePoint.y * extremePoint.y) var circleMaskPathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius)) // 5 var maskLayer = CAShapeLayer() maskLayer.path = circleMaskPathFinal.CGPath toViewController.view.layer.mask = maskLayer // 6 var maskLayerAnimation = CABasicAnimation(keyPath: "path") maskLayerAnimation.fromValue = circleMaskPathInitial.CGPath maskLayerAnimation.toValue = circleMaskPathFinal.CGPath maskLayerAnimation.duration = self.transitionDuration(self.transitionContext!) maskLayerAnimation.delegate = self maskLayer.addAnimation(maskLayerAnimation, forKey: "CircleAnimation") }
一步步的解釋:
實現animation代理的方法:
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) { self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled()) self.transitionContext?.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view.layer.mask = nil }
如今就能夠用CircleTransitionAnimator來實現動畫的效果了。修改代碼NavigationControllerDelegate的代碼:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate{ func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CircleTransitionAnimator() } }
運行起來吧。點擊黑色的按鈕,動畫效果就出現了。
感受不錯吧,可是這個是不夠的!
給動畫添加手勢響應
咱們還要給這個動畫添加一個能夠響應手勢的transition。響應手勢須要用到一個方法:navigationController->interactionControllerForAnimationController。這是UINavigationControllerDelegate中得一個方法。這個方法返回一個實現了協議UIViewControllerInteractiveTransitioning的對象。
iOS的SDK中提供了一個UIPercentDrivenInteractiveTransition的類。這個類實現了上面的協議,而且提供了不少其餘的手勢處理實現。
在NavigationControllerDelegate類中添加如下的屬性和方法:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate{ var interactionController: UIPercentDrivenInteractiveTransition? func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CircleTransitionAnimator() } func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return self.interactionController } }
既然是響應手勢的,那麼一個pan的手勢是必不可少的了。不過首先要添加一些輔助的東西。
1. 在NavigationControllerDelegate中添加對navigation controller的引用。
@IBOutlet weak var navigationController: UINavigationController?
給這個引用添加對navigation controller的引用,如圖:
實現awakeFromNib方法:
override func awakeFromNib() { super.awakeFromNib() var pan = UIPanGestureRecognizer(target: self, action: "panned:") self.navigationController!.view.addGestureRecognizer(pan) }
當pan這個動做在navigation controller的view上發生的時候就會觸發panned回調方法。給這個方法添加以下代碼:
func panned(gestureRecognizer: UIPanGestureRecognizer){ switch gestureRecognizer.state { case .Began: self.interactionController = UIPercentDrivenInteractiveTransition() if self.navigationController?.viewControllers.count > 1 { self.navigationController?.popViewControllerAnimated(true) } else{ self.navigationController?.topViewController.performSegueWithIdentifier("PushSegue", sender: nil) } case .Changed: var translation = gestureRecognizer.translationInView(self.navigationController!.view) var completionProgress = translation.x / CGRectGetWidth(self.navigationController!.view.bounds) self.interactionController?.updateInteractiveTransition(completionProgress) case .Ended: if gestureRecognizer.velocityInView(self.navigationController!.view).x > 0 { self.interactionController?.finishInteractiveTransition() } else{ self.interactionController?.cancelInteractiveTransition() } self.interactionController = nil default: self.interactionController?.cancelInteractiveTransition() self.interactionController = nil } }
在Begin中,pan手勢一開始執行就初始化出UIPercentDrivenInteractiveTransition對象,並做爲值賦給屬性self.interactionController。
Changed,在這個方法中根據手勢移動的距離讓動畫移動不一樣的距離。這裏apple已經替咱們作了不少。
Ended,這裏你會看到手勢的移動速度。若是是正則transition結束,若是是負則取消。同時,把interactionController值設置爲nil。
default,若是是其餘的狀態就直接取消trnasition並把interactionController值設置爲nil。
運行程序,在屏幕上左右移動你的手指看看效果吧!