iOS

深入了解 UINavigationController 的子控制器栈

Posted by Miaocf on June 22, 2020

1.遇到一个需求

使用UINavigationController push进入多个页面后,不再显示部分页面,直接返回到指定的页面。

根据描述,第一个想到的方案就是:

1
 [self.navigationController popToViewController:toVC animated:YES];

通过遍历UINavigationControllerviewControllers找到要跳转的页面,直接pop过去。

遇到的问题就是,iOS 触发返回页面有两种交互方式:

  1. 点击左上角的返回按钮
  2. 使用手势,在屏幕边缘从左往右滑动

要想使用pop的API,就需要拦截边缘滑动的交互事件,重写UINavigationController的手势,(有的朋友说可以屏蔽右滑手势…)
由于不想改动 UINavigationController的手势事件,遂有了下一个方案。

2.UINavigationController实现原理

在iOS开发过程中,UINavigationController 是常用的容器类Controller,UINavigationController 交互方式如下图,

根据官方文档说明,UINavigationController通过管理一个用数组实现的栈结构来管理childControllers 。 将UIViewController依次压入栈中,记录了页面的先后顺序,同时栈持有子视图,实现了内存的管理。

3.解决方案

于是想到,通过修改UINavigationController栈内元素来控制页面的跳转。

1
2
3
4
5
6
    NSMutableArray *vcStack = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
    if (vcStack.count > 2 && vcStack.count > index) {
        //将 元素直接从 数组中 移除
        [vcStack removeObjectAtIndex:index];
        self.navigationController.viewControllers = vcStack;
    }

这样,在进入新的页面时,将不再显示的页面从栈中移除掉,便可实现跨页面的跳转。

需要注意的是:

  1. 当把一个UIViewController的实例push进栈后,该实例的parentViewController就是UINavigationController
  2. 调用UIViewController- (void)removeFromParentViewController方法,会将该实例从栈中移除,但会发生异常,具体表现为点击返回,navigationBar 有动画,但不会有页面跳转
  3. 注意内存的引用,如果要移除的UIViewController 在其他地方存在引用,将不会释放内存,发生内存泄露。

附:UI结构图

参考资料:
UINavigationController

关于转载

知识共享许可协议

本作品采用知识共享署名 4.0 国际许可协议 进行许可。 转载时请注明原文链接。图片在使用时请保留图片中的全部内容,可适当缩放并在引用处附上图片所在的文章链接。