对于iOS开发来说,Navigation和TabBar是最常用到的界面元素。我在前面几篇博客中《iOS开发——代码生成TabBar与视图切换详解》《iOS纯代码实现界面建立、跳转、导航栏(无storyboard、无nib)》《iOS开发——界面跳转与返回及视图类型详解》中较为详细的实现了导航栏和TabBar的实现,以及一些界面跳转的实现。但是,在实际开发中,越来越发现Navigation和TabBar非常的博大精深。今天我们继续较为深入和全面的对需要Navigation和TabBar的项目机型基础的架构。并对界面栈的重构做一个深入的了解。
个人建议,在目前的iOS开发现状下,还是推荐使用xib来构建界面,纯代码和storyboard还不是十分推荐(个人看法)。所以在该篇博客的实现中,我使用xib来实现代码。项目源代码上传至 https://github.com/chenyufeng1991/MoreNavigation 。里面已经有一些代码注释,对于理解还是比较有用的。
(1)Navigation+Tab构建项目
目前市面上大多数的iOS App,都是使用Navigation+Tab的方式来构建,这基本上是一种主流趋势。因为有了这两个工具,可以方便的对程序的流程和页面进行控制,整体的逻辑结构也会变得清晰。我的项目主要使用xib来构建项目,已经删除了自带的Main.storyboard。
在AppDelegate.h中声明一个TabBar属性:
@property (strong, nonatomic) UITabBarController *tabController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //如果本身默认使用storyboard,就不需要window的初始化 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.tabController = [[UITabBarController alloc] init]; FirstViewController *first = [[FirstViewController alloc] init]; first.title = @"第一页"; SecondViewController *second = [[SecondViewController alloc] init]; second.title = @"第二页"; ThirdViewController *third = [[ThirdViewController alloc] init]; third.title = @"第三页"; //分别管理三个不同的栈,其实就是三个不同的Navigation UINavigationController *naviFirst = [[UINavigationController alloc] initWithRootViewController:first]; UINavigationController *naviSecond = [[UINavigationController alloc] initWithRootViewController:second]; UINavigationController *naviThird = [[UINavigationController alloc] initWithRootViewController:third]; self.tabController.viewControllers = @[naviFirst,naviSecond,naviThird]; self.window.rootViewController = self.tabController; //设置为主window并显示,否则启动后是黑屏 [self.window makeKeyAndVisible]; return YES; }
(2)Navigation和TabBar的显示与隐藏设置
当我经过(1)中的执行后,会发现App中的每个页面都有Navigation和Tab,有时候这并不符合我们的要求,在某些页面需要隐藏Navigation或者Tab,可以进行如下设置。
某个页面要隐藏导航栏:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; //主界面隐藏导航栏,推荐使用这种方式来设置 [self.navigationController setNavigationBarHidden:YES animated:animated]; //打印此时的界面栈 [GlobalKit viewControllersArray:self]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.navigationController setNavigationBarHidden:NO animated:animated]; }
隐藏某个页面的TabBar:
FourthViewController *vc = [[FourthViewController alloc] init]; //进入二级界面,隐藏TabBar vc.hidesBottomBarWhenPushed = YES; [self.navigationController pushViewController:vc animated:YES];有时候在pop的时候需要显示或者隐藏TabBar,也可以通过hidesBottomBarWhenPushed设置。
(3)pop到界面栈中存在的某个界面
对于iOS中的界面栈,我们需要有一个明确的认识。界面不断push的过程,其实就是界面不断入栈的过程,当前显示的界面,就是栈顶界面。界面pop的过程,就是界面不断出栈的过程,出栈后的界面也就不可见了。可见的永远都是栈顶界面。系统默认的返回键或者pop方法都是返回到上一个界面。但是在实际开发中,我们有这样的需求,我们需要pop到隔着好几个界面的前面某个界面,那么应该怎么做呢?
//判断前面有没有SecondViewController,有的话pop到SecondViewController页面,否则不进行跳转 for (UIViewController *vc in self.navigationController.viewControllers) { if ([vc isKindOfClass:[SecondViewController class]]) { [self.navigationController popToViewController:vc animated:YES]; return; } }
(4)重构界面栈,pop到之前不存在的界面
为什么要重构界面栈?在实际开发中会遇到这样的情况,在某几个界面间是一个死循环,在进行跳转的时候不断在几个界面间push,然后同样一个界面就会在栈中出现很多次,虽然这些栈都是不同的实例,但是这样完全没有必要。栈中界面越来越多,难道不会影响性能吗?并且默认有这样的准则:一个VC在栈中只有一个。所以,在必要时候,我们不要老是push,如果一个界面已经出现过,我们可以尝试pop。甚至没有出现过,我们可以在栈中插入一个界面,然后pop到该界面。这是为了从性能上去考虑,我们要不断维护界面栈。界面栈其实就是一个数组,插入删除操作非常方便,但是同样要提高警惕,容易造成crash。
/** * 这里的需求是,判断我的界面栈前面有没有FirstViewController,如果有的话,在FirstViewController后面插入一个InsertViewController,然后从当前界面pop到InsertViewController,InsertViewController可以pop到FirstViewController。 否则,点击按钮不执行任何操作 * */ - (IBAction)insertButtonClicked:(id)sender { NSMutableArray *pageArray = [self.navigationController.viewControllers mutableCopy]; for (int i = 0; i < pageArray.count; i++) { id vc = pageArray[i]; //找到要插入页面的前一个界面,这里就是FirstViewController if ([vc isKindOfClass:[FirstViewController class]]) { InsertViewController *insert = [[InsertViewController alloc] init]; //插入界面栈 [pageArray insertObject:insert atIndex:i + 1]; [self.navigationController setViewControllers:pageArray animated:NO]; //打印出当前的界面栈 [GlobalKit viewControllersArray:self]; insert.hidesBottomBarWhenPushed = YES; [self.navigationController popToViewController:insert animated:YES]; return; } } }
界面栈其实是由Navigation来获取的。每一次push就是进栈,每一次pop就是出栈。使用self.navigationController.viewControllers即可。我在GlobalKit中已经封装了方法。需要在查看界面栈的地方调用该方法即可,然后就可以打印该数组了。
//打印当前界面的栈,使用方便 + (void)viewControllersArray:(UIViewController *)viewController { NSArray *vcArray = viewController.navigationController.viewControllers; NSLog(@"\n%@---%@",viewController,vcArray); }