介绍
UIKit是一个非常强大的框架,并提供了在视图控制器之间进行转换的各种方法。 UIKit提供的一些动画包括水平滑动(通过推键),垂直滑动,交叉淡入淡出和页面卷曲。 但是,有时候,您需要在视图控制器之间进行自定义过渡,以创建引人注目的设计或提供独特的用户体验。 自定义转换的一个很好的例子是选择照片时使用Apple的iOS照片。
在本教程中,我将向您展示如何通过使用多个UIKit API创建自己的自定义过渡。
要求
在iOS 7中引入了用于创建自定义过渡的API时,本教程使用了Auto Layout和Swift 2,这意味着您需要在OS X Yosemite或更高版本上运行Xcode 7+。 您还需要从GitHub下载入门项目。
1.自定义过渡的组成部分
实施自定义视图控制器转换时,需要注意两个主要组件:
- 动画控制器 ,也称为动画器
- 过渡委托 ,您分配的视图控制器
动画师对象负责为视图制作动画的持续时间和实际逻辑。 您的应用中的动画控制器可以是任何类型的对象,只要它符合UIViewControllerAnimatedTransitioning
协议即可。
过渡委托负责提供用于自定义过渡的动画控制器。 您指定的委托对象必须符合UIViewControllerTransitioningDelegate
协议。
2.创建自定义过渡
打开入门项目并运行您的应用程序。 当您点击“ 按查看”按钮时,当前正在使用标准的垂直模式过渡。
通过从“ 文件”菜单中选择“ 新建”>“文件...”来创建一个新文件。 从显示的选项中,选择“ iOS”>“源”>“ Swift 文件” ,并将文件命名为CustomTransition 。 该文件将保留自定义转换所需的逻辑。
首先,我们将定义将用于自定义过渡的动画控制器类。 将以下代码添加到CustomTransition.swift中 :
import UIKit
enum TransitionType {
case Presenting, Dismissing
}
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var duration: NSTimeInterval
var isPresenting: Bool
var originFrame: CGRect
init(withDuration duration: NSTimeInterval, forTransitionType type: TransitionType, originFrame: CGRect) {
self.duration = duration
self.isPresenting = type == .Presenting
self.originFrame = originFrame
super.init()
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return self.duration
}
}
我们定义了TransitionType
枚举,该枚举在创建AnimationController
对象时使用。
接下来,我们定义带有一些属性的AnimationController
类。 duration
属性将用于确定动画的持续时间,并且是UIViewControllerAnimatedTransitioning
协议方法transitionDuration(_:)
返回的值。 该持续时间不必是变量,但是在创建动画控制器时仅设置一次即可更改。 isPresenting
和originFrame
属性都将用于创建过渡的动画。
此时,Xcode应该会向您显示错误。 这是因为我们尚未实现UIViewControllerAnimatedTransitioning
协议的必需方法。 在实现此方法之前,您需要了解一件事。
自定义过渡开始时,UIKit将为您提供一个容器视图,您必须在其中执行过渡的动画。 在此容器视图中,您必须手动添加要转换到的视图控制器的视图。 此容器视图仅在过渡期间有效,并且动画完成后立即从视图层次结构中删除。
现在,我们将实现自定义动画。 将以下方法添加到AnimationController
类中:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()!
let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
let toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view
let detailView = self.isPresenting ? toView : fromView
if self.isPresenting {
containerView.addSubview(toView)
} else {
containerView.insertSubview(toView, belowSubview: fromView)
}
detailView.frame.origin = self.isPresenting ? self.originFrame.origin : CGPoint(x: 0, y: 0)
detailView.frame.size.width = self.isPresenting ? self.originFrame.size.width : containerView.bounds.width
detailView.layoutIfNeeded()
for view in detailView.subviews {
if !(view is UIImageView) {
view.alpha = isPresenting ? 0.0 : 1.0
}
}
UIView.animateWithDuration(self.duration, animations: { () -> Void in
detailView.frame = self.isPresenting ? containerView.bounds : self.originFrame
detailView.layoutIfNeeded()
for view in detailView.subviews {
if !(view is UIImageView) {
view.alpha = self.isPresenting ? 1.0 : 0.0
}
}
}) { (completed: Bool) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
我们通过使用containerView()
方法从提供的过渡上下文中检索容器视图来开始此方法。 我们通过调用viewControllerForKey(_:)
方法viewControllerForKey(_:)
分别传入UITransitionContextFromViewControllerKey
和UITransitionContextToViewControllerKey
来访问from和to视图。
在iOS 8及更高版本中,您可以使用viewForKey(_:)
方法以及UITransitionContextFromViewKey
和UITransitionContextToViewKey
键直接访问视图。
在方法主体中,我们使用现有的UIView
动画API对细节视图进行动画处理,以增大或缩小。
要注意的最后一件事是在过渡上下文对象上调用的completeTransition(_:)
方法。 动画完成后必须调用此方法,以使系统知道您的视图控制器已完成转换。 此方法接受布尔值作为其单个参数,该布尔值指示转换是否完成。
通过此实现,我们创建了一个功能齐全的动画控制器。 为了实际实现这一点,我们现在需要设置一个过渡委托。
3.分配过渡代表
如前所述, 过渡委托的工作是为两个视图控制器之间的过渡提供动画控制器对象。 过渡的委托可以是您想要的任何对象,但是通常的做法是使呈现视图控制器成为委托。
在CustomTransition.swift中 ,在AnimationController
类定义下面添加以下代码:
extension ViewController: UIViewControllerTransitioningDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
segue.destinationViewController.transitioningDelegate = self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(withDuration: 3.0, forTransitionType: .Dismissing, originFrame: self.image.frame)
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(withDuration: 3.0, forTransitionType: .Presenting, originFrame: self.image.frame)
}
}
通过实现此扩展,我们使ViewController
类符合UIViewControllerTransitioningDelegate
协议并实现三种方法。 首先, prepareForSegue(_:sender:)
用于将当前ViewController
实例指定为目标DetailViewController
对象的transitioningDelegate
。 另外两个方法创建一个AnimationController
对象,以使用我们之前定义的初始化程序来呈现和消除视图控制器。 如果您从这两种方法中的任何一个返回nil
,则将使用默认或标准视图控制器转换。
设置了过渡委托后,就可以构建和运行您的应用了。 这次,当您点击“ 按一下查看”按钮时,您应该看到Xcode图标的大小增加了,其他标签逐渐淡入。同样,当您点击“ 按一下关闭”按钮时,您应该看到相同的动画,但相反。
恭喜你 现在,您已经在iOS上成功创建了第一个自定义视图控制器转换。
4.使过渡互动
为了使自定义过渡更好,我们将使其具有交互性和响应性。 一个很好的例子是UINavigationController
从左边缘向后滑动手势。
要使自定义过渡具有交互性,首先需要具有一个符合UIViewControllerInteractiveTransitioning
协议的对象。 在本教程中,我们将使用UIKit提供的已经符合该协议的类UIPercentDrivenInteractiveTransition
。
为了在视图控制器之间轻松进行通信(过渡委托和视图控制器确定过渡完成的百分比),请打开DetailViewController.swift并将以下属性添加到DetailViewController
类中:
var rootViewController: ViewController!
接下来,将以下代码添加到DetailViewController
类的didPanDown(_:)
方法中:
@IBAction func didPanDown(sender: UIPanGestureRecognizer) {
let progress = sender.translationInView(self.view).y/self.view.frame.size.height
switch sender.state {
case .Began:
self.rootViewController.interactionController = UIPercentDrivenInteractiveTransition()
self.dismissViewControllerAnimated(true, completion: nil)
case .Changed:
self.rootViewController.interactionController?.updateInteractiveTransition(progress)
case .Ended:
if progress >= 0.5 {
self.rootViewController.interactionController?.finishInteractiveTransition()
} else {
self.rootViewController.interactionController?.cancelInteractiveTransition()
}
self.rootViewController.interactionController = nil
default:
self.rootViewController.interactionController?.cancelInteractiveTransition()
self.rootViewController.interactionController = nil
}
}
在didPanDown(_:)
方法中,我们根据用户相对于详细视图的平移程度来计算progress
变量的值。 如果平移刚开始,我们将创建交互控制器对象,然后开始关闭视图控制器。 每当平移手势在视图中移动时,我们都会向交互控制器更新过渡应该进行的距离。
最后,当平移结束时,我们分别使用finishInteractiveTransition()
和cancelInteractiveTransition()
方法完成或取消过渡。
接下来,返回CustomTransition.swift并将ViewController
类扩展中的prepareForSegue(_:sender:)
方法替换为以下内容:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
segue.destinationViewController.transitioningDelegate = self
(segue.destinationViewController as? DetailViewController)?.rootViewController = self
}
在prepareForSegue(_:sender:)
,我们授予详细视图控制器对根视图控制器的访问权限。
最后,将以下方法添加到ViewController
扩展中:
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self.interactionController
}
interactionControllerForDismissal(_:)
方法返回根视图控制器的interactionController
。 如果为nil
(例如,如果轻按了按钮),则将改用自定义动画。
如您所料,通过实现interactionControllerForPresentation(_:)
方法,在呈现视图控制器时也可以使用交互式控制器。
上一次构建并运行您的应用程序,并在显示详细视图控制器之后,在屏幕上向下拖动,您将看到过渡与手指位置同步移动。
结论
现在,您应该可以轻松地在iOS上创建完全交互式的自定义视图控制器过渡。 如您所见,这些API仅受UIKit和Core Animation的动画功能阻碍。 它们几乎可以用于任何类型的转换。 与往常一样,在下面的评论中留下您的评论和反馈。