前言
最近在开发过程中遇到了一些问题,是关于页面跳转的,查了网上的相关资料,发现大家说的不是很完整,决定自己总结一下。话不多说,进入正题:页面间跳转/切换的方式主要有以下四种:
1.模态弹出。
2.UINavigationController的堆栈式Push和Pop。
3.改变Window的根视图。
4.改变window。(没错,window也是可以改变/更换的)
一、模态弹出
首先,关于模态弹出页面的跳转方式,我们必须了解清楚presentingViewController和presentedViewController这两个属性。楼主英文水平有限,仅凭文档注释并不能真正理解。所以亲自做了测试验证(验证过程很简单,在每一个跳转的页面打印self.presentingViewController和self.presentedViewController,在此不多赘述)。【验证结果】:presentsViewController 属性返回父节点(上一级页面),presentsViewController 属性返回子节点(下一级页面),如果没有父节点或子节点,返回null。切记:这两个属性返回的是当前节点(当前页面)直接相邻父子节点(上下页面),并不是返回最底层(根视图页面)或者最顶层(第一相应者页面)的节点。(括号里的注解是楼主更好理解的说法)
1.最基础的A弹出B(A->B)
[self presentViewController:B animated:YES completion:nil];//从当前界面A到下一个页面B
[self dismissViewControllerAnimated:YES completion:nil];//从B页面模态返回。
【注】细心的程序猿发现:在B页面用self.presentingViewController 调用dismissViewControllerAnimated:YES completion:nil 也能实现从B页面模态返回。那么两者有没有区别呢?答案是:没有区别。文档显示:父节点负责调用dismiss来关闭他弹出来的子节点,你也可以直接在子节点中调用dismiss方法,UIKit会通知父节点去处理。也就是说:[self dismissViewControllerAnimated:YES completion:nil]的实质是通知其父节点VC调用 [self dismissViewControllerAnimated:YES completion:nil];而 [self .presentingViewController dismissViewControllerAnimated:YES completion:nil] 就是直接让其父节点VC执行撤销操作。(有点绕,但是我相信你看的懂)
2.A弹出B,B再弹出C(A->B->C)
从当前界面A到页面B和从B界面到页面C代码是一样的,同样,从C页面模态返回和从B页面模态返回代码也是一样的。(废话,但还是要说一下)
如何直接从C返回到A呢?
A弹出B之后,可不可以再用A弹出C呢?
【警告内容】:“尝试在A上弹C,但是A已经弹了B”。也就是说弹模态视图的时候,只能用最顶层的的控制器去弹,用底层的控制器去弹会失败,并抛出警告。
关于获取viewController的最顶层子节点和最底层父节点,大家可以参考下方的这个,写到我们程序的工具类方法里。
【常见错误】:在viewDidLoad或者viewWillAppear方法中模态页面。
上述代码都会失败,first VC也不会弹出,并会抛出上面的警告。因为self.view还没有被添加到视图树(父视图),不允许弹出视图。也就是说,如果一个的viewController的视图还没被添加到视图树(父视图)上,那么不可以用这个viewController再区弹出其他页面。
二、UINavigationController的堆栈式Push和Pop
UINavigationController的Push/Pop页面和《数据结构》中的堆栈方式很像,遵守栈的特点:先进后出,后进先出。【想象一下】一个狭长的电梯,先进入的人在里面,后进入的靠近门口,出电梯时,靠近门口的(也就是后进入电梯的)先出去,电梯里面的(先进入电梯的)后出去。
需要注意的是push和pop是UINavigationController和其子类才有的方法,普通的控制器是没有的。 所以,所有的Push和Pop操作(出栈/入栈 )都是依赖于self.navigationController 调用的。
1.最基础的A Push出B(A->B)
首先,这里实际指的是NavigationController依次push出AB两个页面。
[self.navigationController pushViewController:B animated:nil];//从当前界面A到下一个页面B(入栈)
[self.navigationController popViewControllerAnimated:YES];//从B页面pop返回。
那么可不可以用self.presentingViewController 来表示要返回的页面呢。于是猜想并尝试如下返回方式:
由于UINavigationController是一个视图控制器的容器,他里面可能放了很多个控制器,而每个控制器之前并不是父/子节点的关系。所以,[self.navigationController popToViewController:self.presentingViewController animated:YES];的返回方式是错误的
【验证】:1.在一个普通的页面P模态弹出一个带有NavigationController的页面。2.在一个带有NavigationController的页面模态一个普通的页面P。
通过打印self.presentingViewController和self.presentedViewController可以发现:页面P的控制器和UINavigationController是属同一级的,其他的均显示null。也就是说我们可以通过判断self.presentingViewController是否为null来确定当前页面创建之前有没有进行过模态弹出操作。
延伸一个判断当前返回方式的方法:
2.A push出B,B push出C
同样,这里实际指的是NavigationController依次push出ABC三个页面。那么想直接从C返回到A怎么实现呢?
同样,不可以使用[self.navigationController popToViewController:self.presentingViewController.presentingViewController animated:YES];的方式。
3.返回到根页面
[self.navigationController popToRootViewController];
4.返回到指定某个页面
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:2] animated:YES];当然,也可以使用上面👆第二条的方法。
三、改变Window的根视图
改变window根视图的方法很简单,创建一个要跳转的页面或NavigationController,然后用setRootViewController方法设置即可。
四、改变Window
在对接一些已经封装好(封装度较高)的第三方SDK时发现:自己程序里已有的NavigationController样式颜色或者TabBarController样式与之冲突,而且修改起来相当麻烦,这个时候建议你直接更换一个Window,然后所有问题完美解决。(如果你真的碰到过这种情况,你一定会感谢我的)
1.更换新的window
2.从新的window页面返回原来的window
【特别用法】:如果你想实现S->A->B->C-...>N,并且在C...N之间的某页面可以直接回到B。可以在B页面模态一个带有NavigationController的页面,之后C...N间的页面跳转用NavigationController的方法,当需要直接返回B时,可以直接调用模态返回方法:[self dismissViewControllerAnimated:YES completion:nil];实现。(是不是很神奇?😂😂)希望对你有所帮助。