程序员

使用presentingViewController属性可能会遇到的坑

场景

假设现在有这样一个场景,我有一个根导航控制器RootNavigationController,它的根视图控制器ViewController有一个子控制器UINavigationController,这个导航控制器的根控制器是FirstViewController

FirstViewController中,添加一个方法使用presentModelViewController的方式跳转到另一个根控制器为NextViewControllerUINavigationController

控制器层级

如果NextViewController中获取它的presentingViewController属性,结果得到的是RootNavigationController

2016-12-18 10:34:09.548 PresentVCDemo[27202:22455163] viewDidLoad 
2016-12-18 10:34:09.549 PresentVCDemo[27202:22455163] viewDidLoad 
2016-12-18 10:34:09.572 PresentVCDemo[27202:22455163] viewDidLoad 
2016-12-18 10:34:09.573 PresentVCDemo[27202:22455163] viewDidLoad 
2016-12-18 10:34:52.785 PresentVCDemo[27202:22455163] Presenting Controller : 

原因

苹果官方的ViewController Programming Guide中关于Presenting a View Controller的部分是这样说的

The view controller that calls the presentViewController:animated:completion: method may not be the one that actually performs the modal presentation. The presentation style determines how that view controller is to be presented, including the characteristics required of the presenting view controller.

也就是说,在调用presentViewController:animated:completion:方法时,真正作为跳转的容器并不一定是调用这个方法的view controller,而是取决于modalPresentationStyle。例如,一个全屏的跳转必须由一个全屏的view controller来完成。如果当前的控制器不能满足,那么系统会自动沿着视图控制器的层级向上查找。

在我们的层级中,只有两个UINavigationController是全屏的,因此,这两个控制器都有可能成为最终跳转的容器。可是为什么最终是RootNavigationController完成了这次跳转而不是FirstViewController的导航控制器呢?

The View Controller Hierachy关于Presented View Controller中找到了这样一句话:

When you present a view controller, UIKit looks for a view controller that provides a suitable context for the presentation. In many cases, UIKit chooses the nearest container view controller but it might also choose the window’s root view controller.

因此,在一开始提到的场景中,UIKit帮我们选择了UIWindow的根视图控制器,而不是FirstViewController的导航控制器。

解决

在某些特定的业务需求中,我们需要利用presentingViewController拿到FirstViewController的导航控制器。

解决这个需求,我们需要用到一对属性

@property(nonatomic,assign) BOOL definesPresentationContext;
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle;

modalPresentationStyle属性决定了将要present的控制器以何种方式展现,默认值为UIModalTransitionStyleCoverVertical

definesPresentationContext就有点神奇了,他的注释文档是这么写的

Determines which parent view controller’s view should be presented over for presentations of type UIModalPresentationCurrentContext. If no ancestor view controller has this flag set, then the presenter will be the root view controller.

简单来说,如果把一个控制器的definesPresentationContext属性设置为YES,那么在需要进行UIModalPresentationCurrentContext类型的跳转的时候,UIKit会使用视图层级内的这个控制器来进行跳转。

FirstViewController中加入下面的代码

[self.navigationController setDefinesPresentationContext:YES];

并在跳转的时候设置目标控制器的modalPresentationStyle

[nav setModalPresentationStyle:UIModalPresentationCurrentContext];
[self presentViewController:nav animated:YES completion:nil];

接下来看一下Log日志

2016-12-18 11:59:20.041 PresentVCDemo[32336:22885768] viewDidLoad 
2016-12-18 11:59:20.042 PresentVCDemo[32336:22885768] viewDidLoad 
2016-12-18 11:59:20.052 PresentVCDemo[32336:22885768] viewDidLoad 
2016-12-18 11:59:20.052 PresentVCDemo[32336:22885768] viewDidLoad 
2016-12-18 11:59:20.920 PresentVCDemo[32336:22885768] Presenting Controller : 

大功告成!现在presentingViewController能够获取到我们期望的对象了。