本节书摘来自异步社区《iOS创意程序设计家》一书中的第6章,第6.2节导航栏控制器UINavigationController,作者 林柏全,更多章节内容可以访问云栖社区“异步社区”公众号查看
6.2 导航栏控制器UINavigationController
iOS创意程序设计家
导航栏控制器(UINavigationController)位于界面的最上方,主要用于将具有因果关系的界面连接起来,它由几个元素组成:左边按钮、右边按钮以及标题。我们可以通过导航栏的navigationItem来访问这3个元素。其中,左右两边的按钮都是UIBarButtonItem类,我们可以通过navigationItem.rightBarButtonItem以及navigationItem.leftBarButtonItem来设置这两个按钮。如果希望界面上不出现任何一边的按钮,那么只要将其设置为nil就可以了。至于中间标题的部分,我们也可以通过titleView来访问。例如:
self.navigationController.navigationItem.titleView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"title"]];
由于titleView本身就是一个UIView,因此,也可以通过addSubview:的方式将其他界面控件加入到标题栏中。
从故事板中,我们可以很清楚地看出导航栏应用程序中界面的组成方式。这些界面形成了界面堆栈,由导航栏负责维护这些界面间的前后关系,如图6.5所示。
6.2.1 界面堆栈的概念
界面堆栈主要负责维护界面间的前后关系,不过,这里所谓的界面是以UIViewController为单位的,而不是UIView。它有点像是界面的历史记录,可以让您回到上一个界面。在UINavigation-Controller这个类中,有一个viewControllers属性,您可以很轻易地通过这个属性来访问界面堆栈,或是通过topViewController来取得位于该堆栈内最上层的界面。
那么,我们怎么将界面推入这个堆栈里面呢?除了通过viewControllers这个属性外,也可以通过pushViewController:animated:来将另一个UIViewController推入到堆栈中。被推入堆栈后的UIViewController所控制的界面就会位于该堆栈内最上层的界面,从而形成了界面切换的效果。pushViewController:在使用的时候有一个限制,其后面的参数只能是UIViewController,而不能是UINavigationController。如果您执意要将UINavigationController推入堆栈,那么应该使用presentViewController: animated: completion:来取代上面的方法。
好了,现在来看看在这个堆栈的过程中发生了哪些事情。当一个UIViewController被置入堆栈里面的时候,它会变为该堆栈里面最上层的界面控制器,当然,导航栏的界面也会随之更新,界面也会自动调整为适当的大小。这个时候,您可以发现导航栏的左边出现了一个按钮,而这个按钮上的文字正是上一个界面的标题。
这个标题的文字其实是在每一个UIViewController设置好的,您可以通过title属性来设置每一个界面的标题。如果忘了设置这些界面的标题,您将看不到返回键,但是,在相对应的位置按下时,这些返回键仍会有作用。除了使用上一页的标题作为返回键的文字外,也可以通过navigationItem.back
BarButtonItem来指定一个自定义的按钮或标题。不论是通过title还是navigationItem.backBar
ButtonItem来设置返回键,您都应该在上一页的UIViewController中设置,而不是在当前的页面内设置。
现在试着查询UIViewController的API,您会发现一件很有趣的事情,原来的UIViewController中就有一个navigationController的属性(而UINavigationContoller中有viewControllers属性)。如果在新建项目的时候不是使用Master-Detail应用程序样板的话,那么这个属性将会是nil,这是因为Master-Detail的应用程序已经帮您做好了关联。关于这一点,大家可以通过编辑器的版本模式来观察。
现在我们来看看相反的操作,也就是让界面回到上一页。可以通过UINavigationController的popViewControllerAnimated:方法来回到上一个界面,或是通过popToRootViewController- Animated:方法来将堆栈中除了最开始的界面外的界面全部退出堆栈。
好了,了解界面堆栈的使用后,我们来看一个实际的例子。您可以先把其他的界面准备好,然后通过UIViewController类的initWithNibName加载进来。例如,可以在XCode产生的ViewController类里面用下面这样的方式来切换界面。
AnotherViewController *anotherViewController= [
[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
[self.navigationController
pushViewController:anotherViewController animated:YES];
我们刚刚提到pushViewController:后面的参数不可以是一个UINavigationController的对象,如果真碰上这种情况,就应该使用以下代码:
[self.navigationController
presentViewController:anotherViewController animated:YES completion:nil];
最后,别忘了设置目前界面的标题,否则界面上就不会出现返回键。
self.title = @"First page";
6.2.2 使用故事板来处理界面堆栈
如果完全采用故事板的方式来设计界面,那么不需要使用诸如pushViewController:animated:的方式来处理界面堆栈。可以直接在两个界面间设置好它们的连接(Segue),然后一切的工作就交由系统自己去处理了。不过有些时候,两个界面间的连接也有可能会有多种情况发生,这通常是因为用户在第1个界面做了某些操作而导致第2个界面出现的结果不同。这时候,我们可以设置多个连接(Segue),然后再由程序去判断要走哪一条路径。
比如,在下面的界面中,我们可以将输入密码的界面与登录成功和登录失败的界面分别连接起来。选中连接后,在属性观测窗口中为每个连接设置一个标识符,在程序里面就可以通过这个标识符来判断要走的路径,如图6.6所示。例如,可以在第一个界面的View Controller中写入如下程序代码。
if( 登录成功 )
[self performSegueWithIdentifier:@"LoginSuccessSegue" sender:nil];
else
[self performSegueWithIdentifier:@"LogiFailSegue" sender:nil];
这样一来,界面就会跟着程序内的逻辑来跑了。
注意:
① 如果尝试将按钮链接到下一个界面,会发现路径只能有一个。如果希望做到上述的效果,那么应该将两个View Controller连接起来才对。
② 除了上述的方式外,也可以在AppDelegate.m中去编写这样的程序代码,以决定第一个界面是什么。其中,“LoginViewController”与“ViewController”是我们为不同的View Controller在属性观测窗口(attribute inspector)所设置的标识符。
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
if( 未记忆密码 )
self.window.rootViewController = [storyboard
instantiateViewControllerWithIdentifier:@"LoginViewController"];
else
self.window.rootViewController = [storyboard
instantiateViewControllerWithIdentifier:@"ViewController"];
6.2.3 单选按钮
iPhone内建的通讯录其实就是一个很典型的导航栏模式,读者们应该也注意到了吧。我们可以看到在导航栏的右边有一个加号的按钮,这是怎么加上去的呢?其实导航栏控制器已经预留了左右两边的按钮。您可以在导航栏的两边各放入一个UIBarButtonItem类的按钮,使用的代码如下:
UIBarButtonItem * item=[[UIBarButtonItem alloc]
initWithTitle:@"Done"
style: UIBarButtonItemStyleDone
target:self action:@selector(clickButtonItem)];
self.navigationController.navigationItem.rightBarButtonItem = item;
self.navigationController.navigationItem.leftBarButtonItem = nil;
把rightBarButtonItem 或leftBarButtonItem设置为nil时,导航栏原有的按钮就不见了,这个技巧可以应用到诸如文本编辑器这样的应用程序上。我们在第5章的UITextViewDemo的例子里面就已经运用了这个技巧。
可以使用的按钮的类型包括以下几种:
typedef enum {
UIBarButtonItemStylePlain,
UIBarButtonItemStyleBordered,
UIBarButtonItemStyleDone,
} UIBarButtonItemStyle;
如果不想使用自定义的按钮样式,也可以直接使用系统内定的按钮类型,不过在初始化的时候得改用initWithBarButtonSystemItem:的构造函数,使用代码如下:
UIBarButtonItem * item=[[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemDone
target:self action:@selector(clickButtonItem)];
系统默认的按钮则有以下几种:
typedef enum {
UIBarButtonSystemItemDone,
UIBarButtonSystemItemCancel,
UIBarButtonSystemItemEdit,
UIBarButtonSystemItemSave,
UIBarButtonSystemItemAdd,
UIBarButtonSystemItemFlexibleSpace,
UIBarButtonSystemItemFixedSpace,
UIBarButtonSystemItemCompose,
UIBarButtonSystemItemReply,
UIBarButtonSystemItemAction,
UIBarButtonSystemItemOrganize,
UIBarButtonSystemItemBookmarks,
UIBarButtonSystemItemSearch,
UIBarButtonSystemItemRefresh,
UIBarButtonSystemItemStop,
UIBarButtonSystemItemCamera,
UIBarButtonSystemItemTrash,
UIBarButtonSystemItemPlay,
UIBarButtonSystemItemPause,
UIBarButtonSystemItemRewind,
UIBarButtonSystemItemFastForward,
} UIBarButtonSystemItem;
值得一提的是,一旦决定使用系统按钮,您将无法改变上面的标题文字;反之,可以通过UIBarButtonItem的title属性来动态地改变上面的文字。除了文字的按钮外,也可以通过图片的方式来呈现,使用代码如下:
UIBarButtonItem * item = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:@"done"]
style:UIBarButtonItemStylePlain
target:self action:@selector(clickButtonItem)];
6.2.4 如何建立导航栏应用程序
在所有的项目样板中只有Master-Detail的应用程序才会默认产生导航栏,在其他的样板中,我们都得自己去建立一个导航栏控制器。下面的内容将介绍如何在设计界面中建立导航栏控制器。可以使用XIB或是故事板来建立这个应用程序,如果可能的话,尽量使用故事板的方式,在开发上会更为直观些。
请记得勾选“Use Storyboard”以及“Use Automatic Reference Counting”选项。
如同往常一样,您应该可以看到一个空白的手机界面。现在,选中这个View Controller,并按下删除键将这个界面删除。现在的界面上应该是空无一物了。
导航栏控制器默认会带有一个UIViewController,而这个UIViewController就是它的界面堆栈内的第一个界面。现在,可以试着在这个UIViewController的标题栏的地方双击后输入它的标题文字。现在的界面看起来应该如图6.7所示。
完成之后,可以试着执行应用程序,您会发现界面上竟然是一片黑色的界面,这是因为我们还没有指定故事从哪边开始的缘故。
每个故事板的一开始一定要有一个起始的界面控制器。所以请选中界面中的Navigation Controller控制器,切换到属性观测窗口,并勾选“Is initial View Controller”,如图6.8所示。现在再试着执行看看,您应该可以看到第一个界面了。
接下来,我们要产生第2个界面。还记得导航栏控制器是将整个UIViewController推入到界面的堆栈里面吧。所以,在这里,我们必须要由控件库中拉入一个非导航栏的控制器。例如一个UIViewController,如图6.10所示。
现在再执行一下您的应用程序,应该可以顺利地由第1个界面切换到第2个界面了。