引言
使用场景
1) 当应用在转场时两个页面的导航栏颜色不同; 如美团的“我的”页面—> 设置页面
2) 转场时前一页面隐藏了导航栏,后一页面显示导航栏;如Twitter“Me”页面 —> 一条Tweet纪录页面
在这两种情况下使用原生的转场效果视觉效果上并不是很友好,很多应用(稍微调查了下手机里使用该转场方式的应用:Mono, Twitter,网易云音乐, 美团,生辰,淘宝等)都是通过类似所示效果显示转场,并支持全屏侧滑Pop页面。
实现思路
主要有三种:
- 自定义导航栏视图: 导航器隐藏导航栏,通过addSubview自定义导航栏,灵活性比较高,缺点是改动很大,原生的很多API都无法使用,比如添加导航栏左右按钮,设置title等都需要自己重写支持。
- 截图覆盖: 通过在转场时,保存截图,覆盖原有页面。缺点是由于实现方案本身的局限性,push时无法添加交互手势换场,只支持pop交互换场。
- 包裹导航栏控制器: 在转场时,对导航栏控制器就行包裹,A—>B时,将导航栏控制器的导航栏隐藏,B则会通过导航栏控制器包裹,由于push时无法push导航栏控制器,因而在B的导航栏控制器外面用UIViewController包裹。从而实现push视图控制器,实现A,B页面的导航栏相互独立。ps: 该思路from jerry的这篇《用Reveal分析网易云音乐的导航控制器切换效果》
JYNavTransition–截图覆盖方式
_奔跑的炸鸡在【iOS】让我们一次性解决导航栏的所有问题这篇文章解决了使用导航栏时的一些常见问题:包括自定义返回按钮,调整返回按钮位置,解决自定义返回按钮导致的侧滑手势失效问题,以及全屏侧滑转场效果。作者给出了实现思路并将全屏侧滑转场封装到KLTAnimateNav中,在跟着写的过程中发现代码中存在一些耦合问题,本着整洁代码的原则进行了重写。
代码地址:JYNavTransition
通过截图覆盖方式独立导航器中的viewControllers之间的导航栏样式,并支持全屏侧滑Pop 方式 ps: 欢迎拍砖交流
使用方式
将JYNavTransition文件夹拖入项目中,导航控制器改用JYNavigationController即可
|
|
特性
- UINavigationController替换为JYNavigationController后,即可支持独立导航器导航栏样式
- 支持开启或者禁用全屏侧滑手势;
- push,pop,popToViewController,popToRoot等方法无需做额外操作。
实现细节
转场时通过截图覆盖原有的toView。需要注意的是需要在导航控制器所在的视图窗口上添加截图才可以遮盖原有的toView和fromView.
- push:在当前导航器的视图所在的窗口添加当前页面的截图,覆盖当前页面,通过UIView动画将fromView逐渐移出屏幕,动画时间为转场时间。
- pop:在当前导航器上添加上一页面的截图覆盖toView,与当前页面的截图覆盖fromView。通过UIView动画进行转场。
- 侧滑返回上一页面: 导航栏在push到下一页面时,添加侧滑手势,添加相应的手势动画。
ViewController转场
转场相关协议
自定义转场相关的API定义在UIViewControllerTransitioning.h
中,相关协议为5个
|
|
UIPercentDrivenInteractiveTransition
遵循UIViewControllerInteractiveTransitioning协议的一个类,可以用一个百分比控制交互转场的过程。
模态转场
Modal转场时识别是Present还是Dismiss
X1: containerView.subViews; X2: toVC.isBeingPresented; X3:fromVC.isBeingDismissed
fromView | toView | containerView对象 | X1 | X2 | X3 | |
---|---|---|---|---|---|---|
A—> B | A | B | 0x7f8eb945f830 | A | YES | NO |
B —> A | B | A | 0x7f8eb945f830 | B | NO | YES |
因而可以通过ViewController的isBeingPresented和isBeingDismissed属性识别到底是Present还是dismiss场景。
|
|
了解转场时的视图结构
|
|
备注:表格中显示的是ContainerView.subviews。X1: beforeAddSubview; X2:afterAddSubview; X3: Into Completion; X4: afterCompleteTransition
X1 | X2 | X3 | X4 | 异常 | |
---|---|---|---|---|---|
A —> B: (Custom) | nil | b | b | b | |
B—>A:(Custom)addSubview | b | a,b | a,b | a | 白屏 |
B—>A(Custom)不addSubview | / | / | b | nil | |
A —> B:(Full) | a | a,b | a,b | b | |
B —> A:(Full)addSubview | b | b,a | a,b | nil | |
B —> A:(Full)不addSubview | / | / | b | nil | 闪现白屏 |
Custom模式
Custom模式下containerView并没有担任父视图。Presentation后A视图并没有从视图结构中移除。在Dismissal过程中并不需要将A视图添加到containerView中,即可正常显示。如果添加到containerView,则会出现闪现A视图,然后出现白屏。
猜测:默认屏幕显示A视图,presentation后,添加containerView在A视图之上。 Dismissal过程中,只需将containerView从视图结构中移除即可正常显示。所以,当把A视图添加到containerView中,则在动画执行过程中会出现A视图,但一旦动画执行完毕,UIkit会将containerView移除,而本身在containerView底下的A视图由于添加到containerView中,因而屏幕上就没有A视图。出现白屏。
FullScreen模式
FullScreen模式下containerView担任父视图。 Presentation后A视图会从视图结构(containerView)中移除。dismissal过程中需要将A视图添加到containerView中。 但如果不主动添加也没什么事情,但dismiss的过程中会出现闪现白屏,然后再显示A视图。 猜测是: dismissal过程中,动画执行后执行completeTransition,则会将containerView中的子视图移除,此时屏幕上显示的是白屏。 当animateTransition代理执行完之后,判断containerView是否为空,如果为空,则添加toView到containerView上,因而会闪现A视图。
小结
Custom模式下,Dismissal时无需添加子视图到containerView.FullScreen模式下,Dismissal需要添加子视图到containerView。
导航器转场
Push时视图上方被遮盖
在使用过程中,主页面Push到B页面后,明明有3个cell,但在转场过程中只显示2个cell,结束后才显示3个。一开始是以为TableView有问题,后面才发现是转场动画有问题,cell少了一个并不是tableView没有正确显示,而是在转场过程中由于tableView的origin为(0,0),导致第一行cell被导航栏遮盖。
|
|
以下为转场过程中通过转场上下文获取的FromVStartFrame,fromVEndFrame,toVStartFrame,toVEndFrame。Push时fromVCStartFrame的y为64是因为A页面有导航栏,高度为455是因为A页面有导航栏与TabBar,因而页面高度需减去导航栏与TabBar的高度。toEndFrame的高度为504是因为B页面无TabBar。
fromVStartFrame | toVStartFrame | fromVEndFrame | toVEndFrame | |
---|---|---|---|---|
A—>B(present) | {{0, 0}, {320, 568}} | {{0, 0}, {0, 0}} | {{0, 0}, {0, 0}} | {{0, 0}, { 320, 568}} |
B—>A(dismiss) | {{0, 0}, { 320, 568}} | {{0, 0}, {0, 0}} | {{0, 0}, { 320, 568}} | {{0, 0}, { 320, 568}} |
A—>B(Push) | {{0, 64}, { 320, 455}} | {{0, 0}, {0, 0}} | {{0, 0}, {0, 0}} | {{0, 64}, { 320, 504}} |
B—>A(Pop) | {{0, 64}, { 320, 504}} | {{0, 0}, {0, 0}} | {{0, 0}, {0, 0}} | {{0, 64}, { 320, 455}} |
之前在实现JYNavTransition时由于测试的视图直接设置背景色为单色,没有发现在Push转场时需将toVC.view的位置设为toVEndFrame。修改后如下:
|
|
参考资料
WWDC 2013 Session笔记 - iOS7中的ViewController切换—Onev // 自定义Modal转场Demo
View Controller 转场—Objc中国 // 自定义导航栏控制器转场Demo
相关开源项目
KLTAnimateNav // 通过截图方式实现,支持全屏返回
JTNavigationController // 通过包裹导航栏控制器实现,支持全屏返回
FDFullscreenPopGesture // 使用原生的 UINavigationController,在 - (void)viewWillAppear
中做处理,支持全屏返回
RTRootNavigationController // 细节完善的比较好,支持unwind,Interface Bulider