[iOS]自定义ViewController的转场动画 (Swi
Presentation ViewController
基础知识
在没有UINavigationController
的时候,我们通常用present modally
(弹出模态控制器)的方式切换视图。默认情况下,目标视图从屏幕的下方弹出。具体方法是:
通过presentViewController(_: animated:completion:)
来弹出视图,
通过viewController
的modalTransitionStyle
的属性设置弹出ViewController
时的动画:
viewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
presentViewController(secondViewController, animated: true, completion: nil)
系统自带的四种动画有:
enum UIModalTransitionStyle: Int {
case CoverVertical // 底部滑入,默认
case FlipHorizontal // 水平翻转
case CrossDissolve // 隐出隐现
case PartialCurl // 翻页
}
自定义转场动画
在转场的过程中系统会提供一个视图容器containerView
,当前的视图(fromView)会自动加入到这个容器中,我们所要做的就是将目标视图(toView)加入到这个容器中,然后为目标视图的展现增加动画。
需要注意的是:如果是从 A 视图控制器 present 到 B,则A是fromView,B是toView。从 B 视图控制器dismiss到 A 时,B 变成了fromView,A是toView。
创建动画管理器类,该类需继承NSObject并遵循UIViewControllerAnimatedTransitioning协议:
import UIKit
class FadeAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
// 指定转场动画持续的时间
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return duration
}
// 实现转场动画的具体内容
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// 得到容器视图
let containerView = transitionContext.containerView()
// 目标视图
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
containerView.addSubview(toView)
// 为目标视图的展现添加动画
toView.alpha = 0.0
UIView.animateWithDuration(duration,
animations: {
toView.alpha = 1.0
}, completion: { _ in
transitionContext.completeTransition(true)
})
}
}
然后在ViewController(第一个VC)中加入如下代码:
// 声明一个动画实例
let transition = FadeAnimator()
// 遵循 UIViewControllerTransitioningDelegate 协议,并实现其中的两个方法:
extension ViewController: UIViewControllerTransitioningDelegate {
// 提供弹出时的动画
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transition
}
// 提供消失时的动画
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transition
}
}
@IBAction func register(sender: AnyObject) {
var viewController = storyboard!.instantiateViewControllerWithIdentifier("SecondViewController") as SecondViewController
viewController.transitioningDelegate = self
presentViewController(viewController, animated: true, completion: nil)
}
最终效果如下:
效果展示Segue
通过Segue转场,即通过在Storyboard中拖线的方式进行转场。自定义转场动画的关键就是:
- 创建一个UIStoryboardSegue的子类,并重载其中perform方法。
- 在perform方法中实现自定义动画的逻辑。
- 将这个UIStoryboardSegue类与拖的segue线进行绑定。
下面我们一步步来实现:
创建两个UIViewController类——FirstViewController、SecondViewController,分别对应Storyboard中的两个ViewController场景,从第一个VC向第二个VC拖一个Segue,并选择custom:
Storyboard.png创建一个UIStoryboardSegue子类,并与上一部的Segue进行绑定,并指定它的Identifier:
Segue.png打开FirstCustomSegue.swift,在perform方法中实现具体动画:
import UIKit
class FirstCustomSegue: UIStoryboardSegue {
// 重载perform方法,在这里我们添加想要的自定义逻辑
override func perform() {
// 得到源控制器和目标控制器的视图
var sourceView = self.sourceViewController.view as UIView!
var destinationView = self.destinationViewController.view as UIView!
// 得到屏幕的宽和高
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// 把destinationView放在sourceView的下面
destinationView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
let window = UIApplication.sharedApplication().keyWindow
// 把目标视图添加到当前视图中
window?.insertSubview(destinationView, aboveSubview: sourceView)
UIView.animateWithDuration(0.4, animations: { () -> Void in
sourceView.frame = CGRectOffset(sourceView.frame, 0.0, -screenHeight)
destinationView.frame = CGRectOffset(destinationView.frame, 0.0, -screenHeight)
}, completion: { _ in
// 展示新的视图控制器
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
})
}
}
在FirstViewController中执行这个自定义转场,这里我们采用滑动手势:
override func viewDidLoad() {
super.viewDidLoad()
var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showSecondViewController")
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeGestureRecognizer)
}
func showSecondViewController() {
self.performSegueWithIdentifier("FirstCustomSegue", sender: self)
}
解除转场
与转场对应的是解除转场(unwind segue),和正常转场一样,也需要创建一个UIStoryboardSegue的子类,并重载其中perform方法。
不同的是,解除转场需要在第一个ViewController中创建一个带UIStoryboardSegue类参数的IBAction方法,这个方法的内容可以为空,但必须存在。
需要注意的是,FirstViewController没有UINavigationController的时候解除转场的动画才会出现,但是正常转场效果是都有的,不知道这是不是个漏洞,知道的老师可以告诉下,谢谢。
下面我们来实现这个自定义解除转场:
打开FirstViewController.swift,添加代码:
@IBAction func backFromSegueAction(sender: UIStoryboardSegue) {
}
打开Storyboard,在SecondViewController场景中进行如下操作:
1.png然后在Scene中会出现Unwind segue,选中后在属性中设置Identifier:backFromSegue
222.png创建UIStoryboardSegue子类-FirstCustomSegueUnwind,重写其中的perform方法:
import UIKit
class FirstCustomSegueUnwind: UIStoryboardSegue {
override func perform() {
var secondVCView = self.sourceViewController.view as UIView!
var firstVCView = self.destinationViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(firstVCView, aboveSubview: secondVCView)
UIView.animateWithDuration(0.4, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
接下来在FirstViewController中来调用这个子类,打开FirstViewController.swift,重写以下方法:
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue {
if let id = identifier {
if id == "backFromSegue" {
let unwindSegue = FirstCustomSegueUnwind(identifier: id, source: fromViewController, destination: toViewController, performHandler: { () -> Void in
})
return unwindSegue
}
}
return super.segueForUnwindingToViewController(toViewController, fromViewController: fromViewController, identifier: identifier)
}
最后,在SecondViewController中执行这个解除转场,还是采用滑动手势:
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "showFirstViewController")
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipeGestureRecognizer)
}
func showFirstViewController() {
self.performSegueWithIdentifier("backFromSegue", sender: self)
}
}
到这里,整个转场就全部写完了,最后看下效果:
最终效果