iOS开发——使用Navigation和TabBar构造App框架与界面栈的重构

      对于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;

在AppDelegate.m中分别构造Navigation和Tab。我这里的思路是:把TabBar作为容器,有三个Tab页,每个Tab页分别管理自己的Navigation,然后把这个TabBar作为window的根视图。最后让window显示即可。

- (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;
}

注意:window一定要使用makeKeyAndVisible来进行显示,否则App启动后是黑屏。


(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];
}

上面的设置最好在viewWillAppear和viewWillDisappear中配对进行设置。否则会有其他的一些小问题。


隐藏某个页面的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;
        }
    }

以上代码的意思是当前界面栈中是否有SecondViewController,如果有的话,就会pop到SecondViewController,假设当前界面为A界面,那么A界面和SecondViewController之间的所有界面都会出栈。SecondViewController界面就会在栈顶。所以这就是for循环遍历的过程。


(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;
        }
    }
}

(5)获取界面栈

界面栈其实是由Navigation来获取的。每一次push就是进栈,每一次pop就是出栈。使用self.navigationController.viewControllers即可。我在GlobalKit中已经封装了方法。需要在查看界面栈的地方调用该方法即可,然后就可以打印该数组了。

//打印当前界面的栈,使用方便
+ (void)viewControllersArray:(UIViewController *)viewController
{
    NSArray *vcArray = viewController.navigationController.viewControllers;
    NSLog(@"\n%@---%@",viewController,vcArray);
}


你可能感兴趣的:(ios,栈,界面)