模态跳转
需求:A视图控制器中presentB视图控制器,B视图控制器再presentC视图控制器。最后从C视图控制器直接返回到A视图控制器。
1、一些解释
1.1两个常用的方法
/** * 展示模态视图 * * @param viewControllerToPresent 调转的目标控制器 * @param flag 如果flag == NO,那么不会执行任何动画(当然也不会执行自定义动画);如果flag == YES,则有可能执行自定义动画,如果没有自定义动画则会执行系统默认动画。 * @param completion 跳转完成后的回调 */ - (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion;
/** * 关闭模态视图 * * @param flag 如果flag == NO,那么不会执行任何动画(当然也不会执行自定义动画);如果flag == YES,则有可能执行自定义动画,如果没有自定义动画则会执行系统默认动画。 * @param completion 关闭完成后的回调 */ - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion;
1.2两个名词说明
@property(nullable, nonatomic, readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0); @property(nullable, nonatomic, readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
A present B,A.presentedViewController是B,B.presentingViewController是A。
2、dismissViewController在哪里执行?
我们最常使用的一种场景:A视图控制器present跳转到B视图控制器,B视图控制器dismiss回到A视图控制器,所以会想当然的以为dismissViewController这个方法是在B视图控制器里面执行。
标准答案是:在A视图控制器里执行。
但是,在上述场景中,用B视图控制器执行也是可以的,因为系统会自动优化,当B视图控制器没有present过其他视图控制器的时候,dismissViewController方法会自动交给B视图控制器的presentingViewController执行,也就是A视图控制器。
3、多个模态视图之间跳转
在网上也看到一些解答,比如说利用通知让B视图控制器执行dismissViewController方法。其实这样是不行的,原因刚刚已经解释过,对于一个视图控制器X,它执行dismissViewController方法的时候将会关闭它present的模态视图,只有在它没有present过模态视图的时候(但是这里B已经present到C了),才会交给他的presentingViewController执行dismissViewController方法。所以这里如果交给B执行,和直接在C里面执行dismissViewController方法的效果是一样的。
显然一个最简单的解决办法就是利用通知或者代理,在A中执行dismissViewController方法。此时B和C视图控制器会发生什么变化呢?
依然摘录一段苹果的文档做一下解释:
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
也就是说,其实在present多个视图控制器的时候,系统维护了一个栈,以我们现在这个情况为例,从栈底到栈顶依次是A->B->C。当栈中某个位置的视图控制器执行dismissViewController方法的时候,栈中所有在它之上的视图控制器都会被dismiss,不同的是,栈顶的视图控制器将会以动画方式被dismiss,而中间的视图控制器只是简单的remove掉。
这种方法是可行的,因为 dismissViewController总是要在A方法中执行的。不过这样做会遇到代码耦合的问题。
一种低耦合的解决方案:
// C视图控制器触发dismiss方法前添加这么一段代码 UIViewController *rootVC = self.presentingViewController; // rootVC.view.alpha = 0; while (rootVC.presentingViewController) { rootVC = rootVC.presentingViewController; } [rootVC dismissViewControllerAnimated:YES completion:nil];
在循环中连续获取presentingViewController,于是最终可以得到根视图控制器,这里就是A,不过这使得A视图控制器中不用添加任何代码,从而解决了耦合的问题。
这样写的另一个好处是,不管是多少个视图控制器之间的跳转,都可以很方便的完成。
缺点:你会明显的感觉到是从C->B->A,有没有一种完美的解决方案,让用户感觉直接从C->A?
这句代码rootVC.view.alpha = 0;的加入可以让达到C->A的效果,但是Animated的设置就无效了。
还是最完美的解决方法。
4、注意,模态跳转的方法已经变更,新的跳转方法,必须进行相关设置
RevokeApplyViewController *revokeApplyVC = [[RevokeApplyViewController alloc] init]; revokeApplyVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; revokeApplyVC.modalPresentationStyle = UIModalPresentationOverCurrentContext; revokeApplyVC.view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [self presentViewController:revokeApplyVC animated:NO completion:nil];