六游的博客小站
iOS自定义转场之Modal转场
发布于: 2020-06-15 更新于: 2020-06-28 阅读次数: 

iOS中的转场动画一共有三种:Modal转场,Navigation转场,Tabbar转场。这篇文章主要来介绍如何在iOS中自定义Modal转场的动画效果

Modal转场中的一些核心概念

关于命名:

在Modal转场的过程中,iOS对于参与转场的角色有两种命名法。第一种是Presenting-Presented,发起presentation的那个控制器在整个过程中永远命名为presenting,被弹出的那个控制器在整个过程中永远被命名为presented。第二种是from-to,在presented(发起调用,弹出控制器)的过程中,发起的那个为from,要弹出的那个为to,在dismiss(发起返回,返回父级)的过程中,发起返回的那个为from,返回的那个父级为to

转场中的控制器角色

转场中动画如何实施

发生Modal转场动画时,系统会在屏幕上为我们提供一个containerView用来承载涉及转场的两个控制器的view,并且将涉及跳转操作的两个控制器中的view提供给我们操作,它们分别是fromView和toView。我们无需关心该containerView在哪里创建,以及层级如何,我们只需要关心,在我们要进行转场动画的时候,UIKit总会为我们提供一个可用的containerView,并且自动的将fromView添加为containerView的子视图。比如一个Modal跳出的时候,前一个view缩小旋转并向左移动出屏幕,后一个view从右向左滑出的一个转场动画,我们可以使用UIView的animation,在动画实施的时候分别操作fromView的frame与transition属性,以及toView的frame属性来实现这个动画。可以看下面的图片来理解这个过程

转场中的控制器角色

Modal转场动画中的关键角色

UIViewControllerContextTransitioning:动画上下文

作用:

在一次转场开始之前,UIKit会为我们自动创建一个上下文对象,并且填充里面的属性,我们在动画执行的过程中经常需要用到上下文中设置的属性。上下文存在于整个转场的过程中,我们需要通过上下文拿到需要的属性,以及通过上下文通知系统一些时间,比如动画已经完成的事件。该协议只用来声明上下文中有哪些我们可以使用的属性以及方法,我们不能去自定义一个该协议的实现类,因为这么做没有意义,上下文是由系统自动创建并且传递给特定方法的。

定义:

这里只给出部分比较重要的方法或者属性,context中定义了许多东西,但这些东西一般都是用来传递上下文信息或者控制动画过程的,有需要可以去官方文档看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protocol UIViewControllerContextTransitioning {
// 动画进行时系统提供的containerView
var containerView: UIView
// 可以通过这个方法传入不同的key来拿到FromController与ToController
func viewController(forKey: UITransitionContextViewControllerKey) -> UIViewController?
// 传入不同的key来拿到FromView与ToView
func view(forKey: UITransitionContextViewKey) -> UIView?
// 获取一个控制器的view在转场开始时其view所在的初始位置
func initialFrame(for: UIViewController) -> CGRect
// 获取一个控制器的view在转场完成之后应该处于的位置。这是系统给我们提供的一个参考
// 我们应该遵循系统给我们的参考
// 在我们自定义动画的最后应该将fromView与toView移动到其应该的位置
func finalFrame(for: UIViewController) -> CGRect
// 通知系统我们已经完成自定义的转场动画过程,每个自定义动画结束的时候都应该调用该方法
func completeTransition(Bool)
// 指示当前的转场动画是否因为某些原因被关闭
var transitionWasCancelled: Bool

// UIViewControllerContextTransitioning中还定义了一组用来执行可交互动画的方法
// 你不用过于关注下面这些方法,这些方法不会由你自己去调用,而是会依靠于其他的组件去调用
// 在这里写出来,只是为了之后你可以更好的理解交互式动画是怎么实现的
// 是否开启了可交互执行模式的动画,如果没有开启,那么就按照动画执行时间自动渲染动画
var isInteractive: Bool
// 之后是一系列用来控制动画执行关闭完成以及执行进度的方法
func updateInteractiveTransition(CGFloat)
func pauseInteractiveTransition()
func finishInteractiveTransition()
func cancelInteractiveTransition()
}

使用:

  • 通过view(forKey: .from)view(forKey: .to)拿到参与转场的两个控制器的rootView
  • 通过viewController(forKey: .from)viewController(forKey: .to)拿到参与转场的两个控制器
  • 通过finalFrame(for: someController)拿到系统给出的控制器rootView最终应该在的位置的参考。我们在自定义动画的过程中,应当让我们的view朝着这个frame与移动,并且在动画完成时应该设置该frame为view的最终frame
  • 通过transitionWasCancelled属性获知当前的转场动画是否因为意外而取消,在自定义的动画执行过程中,如果发现次属性为true,就需要自己做出相关的处理
  • 通过completeTransition(true)方法调用来显式声明动画转场已经结束,我们在自定义转场动画代码中,在转场正常完成后,必须要主动调用该方法。因为手动调用该方法之后,iOS系统才会感知到转场的结束从而去做一些“善后”工作,这些工作包括:调用presentViewController:animated:completion:方法中用户传入的闭包、将fromView从containerView上清除、调用动画对象的animationEnded方法,通知动画已经结束。

UIViewControllerAnimatedTransitioning

作用:

动画实现器,提供一个将fromView隐藏并显现出toView的动画过程

定义:

1
2
3
4
5
func animationEnded(Bool)
// 返回一次动画执行需要的时间
func transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval
// 在该方法中实现具体的动画效果
func animateTransition(using: UIViewControllerContextTransitioning)

实现一个动画类的过程:

  1. 设置动画需要执行的时间
  2. 实现animateTransition方法

实现animateTransition方法的过程:

  1. 根据上下文参数获取到FromController,ToController,FromView,ToView这些基本信息
  2. 根据上下文参数获取动画结束之后toView的位置frame
  3. 给toView设置合适的位置以及相关属性,同时也给涉及转场的fromView设置合适的相关属性
  4. 将toView添加到containerView中
  5. 使用UIView或者CoreAnimation实现相关的动画,并且在动画的最后,使toView的位置移动到第2步中获取的结束frame处
  6. 在动画结束的completion中,判断转场是否正常结束,如果没有正常结束则进行自己的相关处理,如果正常结束了,那么手动调用上下文的completeTransition(true)方法结束转场

UIViewControllerInteractiveTransitioning:可交互的动画控制器

作用:

代表一个可以控制动画过程的对象,使用该对象可以控制转场动画的进度,来完成一些可交互的动画效果。比如右滑返回,在我们右滑的过程中需要随时控制动画的进度以及停止和结束

定义:

1
2
3
4
5
6
7
8
9
// 开始对一个转场动画应用交互控制器,我们在该方法中可以修改动画上下文的相关属性
// 比如用来指示该动画是否是一个可交互控制的动画的属性
func startInteractiveTransition(UIViewControllerContextTransitioning)
// 指示一个动画开始时是否是可交互的
var wantsInteractiveStart: Bool
// 返回系统完成动画所需要的动画完成曲线
var completionCurve: UIView.AnimationCurve
// 返回系统完成动画所需要的动画速度
var completionSpeed: CGFloat

使用系统提供的实现类:

我们直接自定义类去实现UIViewControllerInteractiveTransitioning这个协议并不是很轻松的,因为我们需要从头开始,自己控制动画的完成曲线和完成速度,这需要我们做非常多的工作才能写出一个完善的动画控制器。系统给我们提供了一个已经实现了这个协议的类,这个类是UIPercentDrivenInteractiveTransition,他给我们提供了一种通过进度值来控制动画运行的便捷的方式,99%的场景下我们单独使用系统给我们提供的这个类已经够了,而不用去自己自定义类了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UIPercentDrivenInteractiveTransition {
// 一些属性,可以通过这些属性获取一些基本信息
var timingCurve: UITimingCurveProvider?
var completionCurve: UIView.AnimationCurve
var duration: CGFloat
var percentComplete: CGFloat
var completionSpeed: CGFloat
var wantsInteractiveStart: Bool
// 用来进行动画控制的方法
// 传入一个0-1的浮点类型,更新动画的进度
func update(CGFloat)
// 暂停动画
func pause()
// 关闭动画
func cancel()
// 完成动画
func finish()
}

我们可以创建出一个UIPercentDrivenInteractiveTransition的对象,然后绑定一些交互事件,比如我们创建一个对象之后,就给当前页面的手势添加一个响应事件,在事件中根据手势滑动的进度来更新动画的进度,以及在合适的地方调用方法关闭动画(比如手势滑动距离不到一半停止)或者完成动画(比如手势滑动距离超过一半后停止)

UIViewControllerTransitionCoordinator:动画协调器

作用:

帮助我们在转场动画进行的同时并行执行其他的动画,是我们可以在转场的时候控制一些其他元素与转场动画同步,实现更加自由的转场动画效果。该类型由系统内部创建,不需要我们提供实现类,因为那并没有意义。我们可以通过controller中一个方法transitionCoordinator()来获取到控制器的这个对象。这个对象会在控制器参与转场之前被系统自动创建,在非转场的情况下调用此方法会返回nil。该方法主要记录转场动画的时间等属性,方便将其他动画与其同步

使用:

  1. 协调器执行与正常转场动画同步的动画
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*需要执行动画的view是位于containerView中的情况*/
    coordinator.animate(alongsideTransition: { context in
    /*要执行的动画的代码*/
    /*直接设置特定UIView的属性,协调器会帮助我们自动同步动画*/
    someView.alpha = 1.0
    }, completion: { context in
    /*动画执行结束之后的block*/
    })

    /*需要执行动画的view是位于containerView之外的情况*/
    coordinator.animateAlongsideTransition(in: someView, animation: { context in
    /*要执行的动画的代码*/
    /*直接设置特定UIView的属性,协调器会帮助我们自动同步动画*/
    someView.alpha = 1.0
    }, completion: { context in
    /*动画执行结束之后的block*/
    })
  2. 协调器执行与交互式动画同步的动画
    1
    2
    3
    4
    5
    /*该方法会注册一个block,并在每次交互式动画控制器状态改变时(比如动画进度)调用*/
    func notifyWhenInteractionChanges { context in
    /*根据现在交互式动画执行的状态来调整自定义的view需要随之变化的属性*/
    someView.alpha = someCalc(context.someProps)
    }

UIPresentationController:Presentation控制器

作用:

其实在iOS系统中,所有通过present弹出的控制器都会包裹在UIPresentationController中。UIPresentationController的作用就是对presentedController提供额外的管理,其内部有一个containerView(注意这个containerView与转场中出现的那个概念是不同的),用来承载presentedController的rootView以及用户额外添加的view。UIPresentationController可以管理presentedController的rootView在containerView中的相对位置与大小,我们可以提供自定义的view作为背景(比如一个模糊的背景view),并且还可以提供额外的手势等交互逻辑(比如触摸周围的透明部分就执行dismiss)

为什么要为自定义的present转场动画中的控制器的modalPresentationStyle属性设置为custom

系统为系统内置的几种转场动画效果提供了不同的UIPresentationController子类实现,比如UIModalPresentationFullScreen类型就对应着内部设置presentedController的frame充满整个containerView的一个UIPresentationController子类实现。再比如UIModalPresentationFormSheet类型对应着一个内部设置presentedController的frame不充满整个containerView,并且在containerView中添加一个暗视图的一个UIPresentationController子类实现。如果我们没有修改控制器的modalPresentationStyle为Custom,系统在转场的时候为目标控制器提供UIPresentationController的时候就可能使用系统内置的其他实现,从而产生一些歧义

如何实现一个自定义的UIPresentationController:

  1. 创建自定义的类继承自系统的UIPresentationController
  2. 重写类中的frameOfPresentedViewInContainerView方法,返回presentedController的rootView在containerView之上的相对位置
  3. 重写类的初始化方法initWithPresentedViewController:presentingController:,在初始化方法中负责创建一些自定义的view,以及添加一些自定义的逻辑(比如手势控制点击rootView之外containerView之上的地方就主动dismiss)
  4. 重写类的方法presentationTransitionWillBegin,该方法会在控制器将要被presented的时候调用,在该方法中将自定义的view添加到containerView上去,并设置一些自定义view的一些动画相关的初始属性(如果有动画效果的话)。注意你不用主动将rootView添加到containerView上,UIKit内部会自动执行这件事情。之后调用presentedController的协调器的方法执行转场时自定义view需要执行的动画效果。
  5. 重写类的方法presentationTransitionDidEnd:,转场动画关闭时调用,如果是正常结束并完成转场则会传入参数YES,如果是中途结束未完成转场(比如交互式动画手势取消)会传入参数NO,针对这种非正常取消的情况我们需要做一些额外操作,比如移除我们之前添加的自定义view
  6. 重写类的方法dismissalTransitionWillBegin,该方法在dismissal将要发生时调用,方法内部的逻辑与第4步中的presentationTransitionWillBegin方法基本一致
  7. 重写类的方法dismissalTransitionDidEnd:,该方法在dismissal将要发生时调用,方法内部的逻辑与第5步中的presentationTransitionDidEnd:方法基本一致

UIViewControllerTransitioningDelegate:转场代理

在哪里使用:

每一个Controller中拥有一个UIViewControllerTransitioningDelegate协议类型的代理属性,用来响应本Controller中Modal转场的相关事件

1
weak var transitionDelegate:UIViewControllerTransitioningDelegate?

定义:

1
2
3
4
5
6
7
8
9
10
11
12
protocol UIViewControllerTransitioningDelegate {
// 控制器被presented时调用会首先调用该控制器的该方法返回一个描述动画的协议类型实例,如果返回nil会直接使用系统的动画方式
func animationController(forPresented: UIViewController, presenting:UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 控制器dismiss返回时会先调用该控制器的该方法返回一个描述动画的协议类型实例,如果返回nil会直接使用系统的动画方式
func animationController(forDismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 询问当前控制器被presented时是否有一个交互式的动画控制器,如果有则会将动画交给这个动画控制器执行,如果没有则会按照原生的动画执行方式,在一个时间间隔内将动画渲染完成
func interactionControllerForPresentation(using: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
// 询问当前控制器调用dismiss返回时是否有一个交互式的动画控制器
func interactionControllerForDismissal(using: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
// 返回转场中需要使用的UIPresentationController
func presentationController(forPresented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
}

Modal自定义转场中的调用流程

Present过程(A Present To B)

  1. 检查B控制器的transitionDelegate是否不为空,如果不为空,代表提供了自定义的转场动画,如果没有则会使用系统默认的动画效果
  2. 访问B控制器的modalPresentationStyle,如果为其他系统中自定义的style,则提供一个与之对应的UIPresentationController,如果为custom,则访问代理的presentationController方法获取自定义的presentationController,如果该方法返回为空,则会返回一个默认实现(rootView的frame充满containerView,无任何其他多余view)
  3. 访问B控制器代理的animationController方法获取一个动画控制器,如果返回为空,则执行系统默认的动画过程
  4. 检查B控制器代理的interactionControllerForPresentation方法是否返回一个非空的交互动画控制器,如果返回了则会更新上下文中相关信息,为交互动画控制做一些准备
  5. 系统准备好所有东西时,转场动画准备开始,调用UIPresentationController的presentationTransitionWillBegin方法
  6. 渲染动画器控制的动画以及向协调器提交的动画
  7. UIKit等待动画渲染完毕并主动调用上下文的completeTransition:方法
  8. 调用UIPresentationController的presentationTransitionDidEnd方法

dismissal过程(B Dismiss To A)

  1. 检查B控制器的transitionDelegate是否不为空,如果不为空,代表提供了自定义的转场动画,如果没有则会使用系统默认的动画效果
  2. 访问B控制器的modalPresentationStyle,如果为其他系统中自定义的style,则提供一个与之对应的UIPresentationController,如果为custom,则访问代理的presentationController方法获取自定义的presentationController,如果该方法返回为空,则会返回一个默认实现(rootView的frame充满containerView,无任何其他多余view)
  3. 访问B控制器代理的animationController方法获取一个动画控制器,如果返回为空,则执行系统默认的动画过程
  4. 检查B控制器代理的interactionControllerForDismissal方法是否返回一个非空的交互动画控制器,如果返回了则会更新上下文中相关信息,为交互动画控制做一些准备
  5. 系统准备好所有东西时,转场动画准备开始,调用UIPresentationController的dismissalTransitionWillBegin方法
  6. 渲染动画器控制的动画以及向协调器提交的动画
  7. UIKit等待动画渲染完毕并主动调用上下文的completeTransition:方法
  8. 调用UIPresentationController的dismissalTransitionDidEnd:方法

如何实现自定以转场动画

  1. 设置目标控制器的modalPresentationStyle属性为custom
  2. 设置目标控制器的transitionDelegate指向一个实现了UIViewControllerTransitioningDelegate协议的对象
  3. 创建一个实现了UIViewControllerContextTransitioning协议的类,该类的实现方法可以参考上面的UIViewControllerContextTransitioning协议介绍中的如何实现一个动画类的过程
  4. 如果是交互式的动画(比如手势拖动控制),那么创建一个继承于UIPercentDrivenInteractiveTransition的子类,在子类中监听手势(或者其他交互方式)的变动,并在合适的地方调用父类的方法控制动画进度
  5. 是否需要使用到UIPresentationController(比如是否需要实现弹出视图并不完全覆盖屏幕,屏幕空白部分需要附加模糊试图,点击屏幕空白部分需要退出等效果),如果需要用到,则创建一个继承于UIPresentationController的子类,重写子类中的相关方法实现你的逻辑。关于实现方法可以参考上面的如何实现一个自定义的UIPresentationController

实际应用

最简单的淡入淡出动画实现

假设A为源控制器,B为跳转的目标控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//首先创建一个动画类,实现UIViewControllerAnimatedTransitioning协议来控制动画
class Animation: NSObject, UIViewControllerAnimatedTransitioning {
//重写transitionDuration方法,返回动画执行的时长
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
//重写animateTransition方法,内部控制动画
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//根据上下文参数获取到FromController,ToController,FromView,ToView这些基本信息
let context = transitionContext
let contentView = context.containerView
let fromVC = context.viewController(forKey: .from)!
let toVC = context.viewController(forKey: .to)!
let fromView = context.view(forKey: .from)!
let toView = context.view(forKey: .to)!
//根据上下文参数获取动画结束之后toView的位置frame
//并在动画开始之前对两个涉及到转场的View设置合适的属性
toView.frame = context.finalFrame(for: toVC)
fromView.frame = context.initialFrame(for: fromVC)
fromView.alpha = 1
toView.alpha = 0
//将toView添加到comtainerView中
contentView.addSubview(toView)
//开始执行动画,动画执行完毕之后必须确保toView的frame在其finalFrame之上
let duration = self.transitionDuration(using: context)
UIView.animate(withDuration: duration, animations: {
fromView.alpha = 0
toView.alpha = 1
}) { finished in
//动画执行结束之后,判断转场是否正常完成,并且根据需要调用上下文的completeTransition方法来释放资源
let cancelled = context.transitionWasCancelled
context.completeTransition(!cancelled)
}

}
}

//之后扩展控制器A类,使自己遵循UIViewControllerTransitioningDelegate协议
extension A: UIViewControllerTransitioningDelegate {
//实现animationController(forPresented:)返回当前控制器被present出来的时候对应的动画器
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print(#function)
print(self)
return Animation()
}
//实现animationController(forDismiied:)返回当前控制器要dismiss回其他控制器的时候对应的动画器
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print(#function)
print(self)
return Animation()
}
}

presentToB() {
let b = B()
//将A控制器自身作为transitionDelegate属性赋值给B控制器示例
b.transitionDelegate = self
//调用present跳转过去
self.present(b, withAnimation: true)
}

可交互的动画应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//实现一个可交互的动画控制器,遵循UIViewControllerInteractiveTransitioning协议
//这里通过直接继承系统类UIPercentDrivenInteractiveTransition来实现
class InteractionAnimation:UIPercentDrivenInteractiveTransition {
weak var context: UIViewControllerContextTransitioning!
let gesture:UIScreenEdgePanGestureRecognizer
//该手势需要一个外部的边缘手势与之配合
init(_ gesture:UIScreenEdgePanGestureRecognizer) {
self.gesture = gesture
super.init()
gesture.addTarget(self, action: #selector(gestureUpdate(sender:)))
}

override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
context = transitionContext
}
//在每次手势触发时,计算当前应该所处的动画进度,并通过父类的update(),finish(),cancel()方法来控制动画进度
@objc func gestureUpdate(sender:UIScreenEdgePanGestureRecognizer) {
switch sender.state {
case .began:
break
case .changed:
self.update(self.percent)
case .ended:
if self.percent > 0.5 {
self.finish()
} else {
self.cancel()
}
default:
self.cancel()
}
}

private var percent:CGFloat {
let contentView = context.containerView
let location = gesture.location(in: contentView)
let w = contentView.bounds.width
let h = contentView.bounds.height
switch gesture.edges {
case .left:
return location.x/w
case .right:
return (w-location.x)/w
case .top:
return location.y/h
default:
return 0
}
}
}

//创建一个动画类,实现UIViewControllerAnimatedTransitioning协议来控制动画
class Animation: NSObject, UIViewControllerAnimatedTransitioning {
//实现transitionDuration方法,返回动画执行的时长
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
//实现animateTransition方法,内部控制动画
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let context = transitionContext
let container = context.containerView
let fromVC = context.viewController(forKey: .from)!
let toVC = context.viewController(forKey: .to)!
let fromView = context.view(forKey: .from)!
let toView = context.view(forKey: .to)!
// 判断是present还是dismiss
let presenting = toVC.presentingViewController == fromVC
// 执行动画
if presenting {
fromView.frame = context.initialFrame(for: fromVC)
toView.frame = context.finalFrame(for: toVC).offsetBy(dx: UIScreen.main.bounds.width, dy: 0)
container.addSubview(toView)
} else {
fromView.frame = context.initialFrame(for: fromVC)
toView.frame = context.finalFrame(for: toVC)
container.insertSubview(toView, belowSubview: fromView)
}

let duration = self.transitionDuration(using: context)
UIView.animate(withDuration: duration, animations: {
if presenting {
toView.frame = context.finalFrame(for: toVC)
} else {
fromView.frame = context.initialFrame(for: fromVC).offsetBy(dx: UIScreen.main.bounds.width, dy: 0)
}
}) { finished in
let cancelled = context.transitionWasCancelled
context.completeTransition(!cancelled)
}
}
}

//扩展控制器A类,使自己遵循UIViewControllerTransitioningDelegate协议
extension A: UIViewControllerTransitioningDelegate {
//实现animationController(forPresented:)返回当前控制器被present出来的时候对应的动画器
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Animation()
}
//实现animationController(forDismiied:)返回当前控制器要dismiss回其他控制器的时候对应的动画器
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Animation()
}
//实现两个interactionController方法来分别返回本控制器被present以及dismiss回原控制器时所用到的动画控制器
//这里我们假设存在一个A,B两个控制器都可以访问到的someGlobalGesture,到了实际项目手势如何处理还需要你们自己结合业务考虑
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
switch someGlobalGesture.state {
case .began, .changed, .recognized:
return InteractionAnimation(someGlobalGesture)
default:
return nil
}
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
switch someGlobalGesture.state {
case .began, .changed, .recognized:
return InteractionAnimation(someGlobalGesture)
default:
return nil
}
}
}
//在A界面出现的时候绑定手势action,手势进入活跃状态时就跳转到B去
//现在我们就可以通过左滑从A进入B了
@objc func presentToB(_ sender:UIScreenEdgePanGestureRecognizer) {
switch sender.state {
case .began:
let b = B()
//将A控制器自身作为transitioningDelegate属性赋值给B控制器示例
b.transitioningDelegate = self
//调用present跳转过去
self.present(b, withAnimation: true)
default:()
}

}
//在B界面出现的时候绑定手势action,手势进入活跃状态时就返回到A
//现在我们可以通过右滑从B返回到A了
@objc func dismissToA(_ sender:UIScreenEdgePanGestureRecognizer) {
switch sender.state {
case .began:
self.dismiss(animated: true, completion: nil)
default:
break
}
}
--- 本文结束 The End ---