IOS开发入门(11)-导航控制器I:层级结构和标签
前言:(直接从书上抄的)
大多数应用程序是由主视图导出多个屏幕,并且通常情况下实现屏幕切换的方法还不止一种。我们需要一种方式来实现用户在应用程序内来回移动,以及让用户知道他们所在的具体部分提供的功能之间的切换。
在iPhone设备上,大多数使用导航控制器的应用程序可以分为两种。一种使用简单的主视图来导航一系列展示或多或少、渐进的细节场景。通讯录、笔记和设置就是不错的示例。其他的,比如闹钟和音乐,则有许多主视图,各自拥有不同的功能,设置拥有自己的内容层次。
iOS提供控制器来管理每种内容之间的导航。UINavigationController用于在各种层次结构的信息之间上下移动,而UITabBarController则处理多个主视图之间的切换。
本节就是介绍这两种方法怎么使用。
1 导航控制器
我们来看一下设置是长啥样的吧,下图展示的是setting方法应用程序中的某个局部的层级结构
在iPhone上,这是常见的组织内容的方式。该方式优雅地解决了如何在狭小空间内显示大量内容的问题。setting允许修改iPhone和setting内部各种应用程序的数以百计的设置选项。采用层次结构的方式来“折叠”内容,能够很好地应用两种屏幕尺寸,以及很好地复合人们通常善于分类和分组的情况。
那些拥有带层次结构的内容的应用程序,工作方式一般基本相同。在屏幕层级的顶部,会有内容概述,然后就是多个带更多细节的屏幕。setting应用程序就是不错的示例。在顶层,一开始是setting应用的所有相关的分类,然后是能够持续向下深入,直到到达特定设置的细节。
UINavigationController正是为了处理这种类型的导航而设。他跟踪客户,就像它位于导航中不同屏幕的层次结构,并提供默认的控件返回。该控制器通过将所有内容都嵌入它内部来实现这一点。
上图显示了导航控制器的一部分。顶部是导航栏。导航栏通常会用在上下文中或展开的当前路径。标题通常为用户正在观看的内容。正如我们很快就会看到的一样,在导航路径中,左手边的后退按钮能够让用户返回上一节。在导航栏上,用户可以自定义按钮
位于底部的是一个可选的工具栏,是另一处让我们设置空间的地方,上面放了一个操作按钮。我们的视图控制器位于灰色地带。而在当前场景的某个地方,需要提供一个方法来打开下一级详细页面。例如,在Add/View场景中的Edit按钮
当使用导航控制器时,一开始可以将它连接到第一个场景或根场景(一个视图控制器)。当导航控制器打开时,他就会打开根场景。正如我们将其关联到一个新的场景(视图控制器),导航控制器会将它放在当前路径上,并显示该场景。
在视图控制器的堆栈中,根视图位于底部,路径通常也会被记录。
介绍一下导航控制类
所有的导航功能都通过5个主要类来提供:
- UINavigationController为当前导航堆栈协调所有的组件。除了管理导航堆栈和所有砖厂(transition)之外,它有指向导航栏和可选工具栏的引用
- UINavigationBar是位于屏幕顶部的导航栏视图。UINavigationController用它显示当前场景的标题和一个按钮,如果需要的话,它可以用于返回前一个界面。它可以独立地使用,但是不推荐这样做
- UINavigationItem管理层次结构中某个特定视图控制器的UINavigationBar。它有如下各项的属性:设置标题,显示、添加和隐藏工具栏按钮,包括Back按钮
- UIBarButtonItem提供了一个对象来管理导航和工具栏上的按钮。从系统提供按钮(如Done(完成)、Cancel(取消)和到文本按钮的操作(action)等),可以创建出任意按钮。Button Item既不是UIButton,也不是视图(View)类型。它们是管理项,可以显示用户界面中的东西。可以将UIButton放在Button Item(按钮项)里面
- UIToolbar管理一个可选的工具栏,通常位于屏幕的底部。其拥有方法与属性来设置Button Item(按钮项)和外观,以及一种方式来指定其位置是在屏幕的顶部还是底部
UINavigationItem使用当前视图控制器的title属性来设置文本。更改title会改变文本。之前在本地化的场景标题我们见过这个操作。
下图显示了在CarValet应用程序的Add/View场景中与导航控制器相关的对象
导航控制器管理Add/View场景以及其他场景。每个被管理的场景有一个UINavigationItem,用于设置导航栏的内容以及标题。Add/View场景的标题栏也有一个工具栏按钮,以显示car images场景。
观看上图的圈圈,这是一个特殊的关联标识符(segue identifier),是为第一个控制器或根视图控制器而设。不想push segue,着开起来想一条两端都有一个圆的线。根场景是导航控制器展示的第一个场景。故事版中的另一个链接显示了场景之间的导航路径。例如在Add/View于Edit场景之间存在一个链接。因为无处可去只有返回,Edit场景就是一个叶节点,如同第一张图的叶节点。
添加工具栏
(从书上抄了那么多字真是类,觉得还是介绍一下比较好),现在开始干正事。
首先我们要把图标添加到项目的资产库中。我们需要图标,可以
在这里下载,但是那需要自己找;也可以在这里我的github下载。最好是从我的github上下载吧。
好了,图标下载完了,要添加到资产库中,如下图操作方式
选+之后,会跳出一个目录,到最下面找到Import,点完import,选中要导入的文件就行啦
- 现在图标加进去了,我们要加底部工具栏了,打开故事面板
- 添加完工具栏,放置按钮
- 选择左边的按钮,Image设置为sign_out;右边那个按钮,Image设置为sign_in;中间的按钮,title改为Edit。
- 删除原来的Previous、Next和Edit按钮,
- 将我们之前添加的左右箭头通过下图方法设置关联,左箭头选择previousCar,右箭头选择nextCar
- 当我们删除Edit按钮时,也删除了到Edit场景的关联(segue),我们要将它恢复,将它拉到Edit场景,选择push,并设置标识符为Editsegue,与之前的Edit按钮所用的关联名称一样
然后运行程序你可能会发现下面的导航栏没有出现,那么在ViewController.m中的viewDidLoad方法中,添加self.navigationController.toolbarHidden = NO;
使他正好位于[super viewDidLoad];
下面,这时候就出现了,但是点开其他的例如car images,下面也出现了导航栏,这显然不是我们想要的,在Edit等场景里面也是这个情况。可以在他们各自的viewDidLoad方法中添加self.navigationController.toolbarHidden = YES;
,这样就默认为隐藏了。然而还有个问题,当我们从其他场景退出返回到Add/View场景时,导航栏消失了,他也是被隐藏了,那我们就换个地方,在ViewController.m中的viewWillAppear方法中,添加self.navigationController.toolbarHidden = NO;
,这样每次加载的时候,就自动设置回去了。这样就设置完成啦。
(PS,颜色是我后来弄的,到这步按钮颜色应该是蓝色的,导航栏应该是白色的,而且没有About)
基于消息的导航
到目前为止,我们已经使用segue在场景之间进行导航,包括使用特使的exit segue。我们拥有一些控制器可以传递数据并能使用prepareForSegue:方法修改行为。借助于UINavigationController的属性和消息,可以拥有完全控制权。可以实现如下类似行为:将新的视图控制器推给堆栈(push),弹出(pop)当前视图控制器,弹出根视图控制器之上的所有控制器,获取当前导航堆栈中的控制器数组,甚至重新排列视图控制器数组。
可以使用基于代码的消息,为CarValet应用程序添加一个About场景,而不是在故事面板中添加,而是可以在单独的用户界面资源文件中创建界面。在Xcode中,这些文件是XIB文件
实现步骤如下:
- 创建一个新的Cocoa Touch Class,命名为AboutViewController,并选中“Also create XIB file”
- 这时候多出了三个文件,将他们放到supporting files的上面
- 打开AboutViewController.xib,这是IB文件,不支持任何脚本功能,如segue何连接。使用Attributes检查器将视图的顶部栏设置为不透明的导航栏,发现导航栏不能编辑
- 使用Lable或其他,自行添加内容
- 打开故事面板,添加一个About按钮。位置看上面的图。
- 在ViewController.m中import AboutViewController.h
- 将About拉到ViewController.h中创建action,名称为aboutCarValet
- aboutCarValet方法代码如下:
- (IBAction)aboutCarValet:(id)sender {
AboutViewController *nextController;
nextController = [[AboutViewController alloc]initWithNibName:@"AboutViewController"//1
bundle:[NSBundle mainBundle]];
nextController.title = @"About CarValet";//2
[self.navigationController pushViewController:nextController animated:YES];
}
注释:
- 为AboutViewController实例初始化next Controller视图控制器。该调用用到了之前为视图控制器界面创建的XIB文件
- 设置About视图的标题。尅本地化该字符串
- 告诉导航控制器将新的视图控制器推到对战中——也就是打开并转场到该场景
运行结果:
添加颜色
我们在IB中,有时候会看到Tint(色调)选项。使用此选项是为用户体验增加彩色主题的快速方法。需要注意的是,它与VIew区域中改变背景色是不同的。
对UINavigationController来说,可以更改导航栏、工具栏和工具栏按钮的色调。为事物设置色调的工作由导航控制器进行管理,导航控制器会改变它管理的每个屏幕上该事物的颜色。
打开故事面板,自行修改颜色。
可能会遇到工具栏色调不起作用,那么我们可以用代码实现,在ViewController.m中的viewDidLoad方法下,添加
UIColor *sky = [UIColor colorWithDisplayP3Red:102.0/255.0 green:204.0/255.0 blue:255.0/255.0 alpha:1.0];
self.navigationController.toolbar.barTintColor = sky;
这时候颜色就添加完啦,本程序还添加了其他颜色,配色方案如下
用户界面元素 | 苹果蜡笔颜色 | RGB |
工具栏 | SKY | R:102 G:204 B:255 |
标题 | snow(white) | R:255 G:255 B:255 |
按钮 | Mocha | R:128 G:64 B:0 |
但是我们上面的方法只能更改一个屏幕里面的颜色,以下代码是更改全部,在AppDelegate.m文件中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UIColor *Mocha = [UIColor colorWithDisplayP3Red:128.0/255 green:64.0/255.0 blue:0/255.0 alpha:1.0];
[[UIButton appearance]setTitleColor:Mocha forState:UIControlStateNormal];
[[UIBarButtonItem appearance] setTintColor:Mocha];
return YES;
}
其中,最后两行是通过appearance protocol协议修改按钮,工具栏按钮颜色。
好了,导航控制器的使用就暂时介绍到这里咯。
1 标签栏控制器
本想找clock程序作为用例的,模拟器上竟然没有。换成健康
看到下面的健康数据,今天,数据来源这些选项了吧,这些是标签栏,每个栏对应不同的功能,而且有的栏里面还有多个屏幕。
工具栏的工作原理
使用UITabBarController时,每个功能区域或标签栏都是根视图,实际上,标签栏控制器是应用程序真正的根视图,简单的画一下。
标签栏控制器使用如下三个主要类:
- UITabBarController管理标签栏选项卡中显示的视图控制器,并且还管理标签栏的用户交换,包括选择处理选中。如果存在5个以上选项卡,那么会展示More按钮。它也管理对额外选项卡的访问,并且管理选项卡的重新排序。
- UITabBar是能呈现1至5个以上的选项卡的视图。每个选项卡都代表应用程序中一个不同的根视图。如果有5个以上的选项卡,那么特殊的More标签放置子在右边
- UITabBarIter是标签栏的单个选项卡,包括两个属性分别用于标题和图像,以及一个属性用来自定义图像在被选中时的行为。
为CarValet添加标签栏
图片什么的之前已经导入了
- 打开故事面板,选中下图
- 选择Editor|Embed In|Tab Bar Controller
(PS,运行到这里,标签栏中应该是只有一个图标,圆形的还是方形的晚了)
- 更换图标
这时候是只有一个选项卡的,我就不运行了。现在我们将car image场景添加为第二个选项卡
- 从Add/View场景中删除Car Images按钮
- 从标签栏拉到Car Images场景,从Relationship Segue类别中选择视图控制器。这是就添加上去了。
- 更换图标
- 这时候运行程序,下面有两个选项卡了(PS我这是完整版,不要在意这些细节。。。)
然而运行的时候发现reset没用
进行以下步骤修改:
- 在编辑器中打开CarImageViewController.h文件,将resetZoomButton的类别改成UIButton
- 在iPhone故事面板中,选择CarImageViewController并将一个正常的按钮拖入到顶层视图,改标题为Reset Zoom
- 设置约束,到顶部为系统默认,到后边缘为0
- 拖拽一个从该按钮到.h文件中的resetZoomButton实例变量的关联
- 拖拽一个从该按钮到car image视图中发送resetZoom消息的关联
- 在与该按钮相对应的Attributes编辑器中的Control部分,将默认值设置为不可用
ok,运行程序就行啦。
最后一点,移动的Info
我们通过代码的形式添加about到标签栏
- 移除Add/View中的about按钮
- 打开ViewController.m并删除对AboutVIewController.h的引用,删除aboutCarValet:方法以及在ViewController.h中的定义
- 打开AppDelegate.m,导入AboutVIewController.h的引用。然后修改代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UIColor *Mocha = [UIColor colorWithDisplayP3Red:128.0/255 green:64.0/255.0 blue:0/255.0 alpha:1.0];
[[UIButton appearance]setTitleColor:Mocha forState:UIControlStateNormal];
[[UIBarButtonItem appearance] setTintColor:Mocha];
UITabBarController *tabBarController = (UITabBarController*)self.window.rootViewController;//1
//2
AboutViewController *aboutViewController = [[AboutViewController alloc]initWithNibName:@"AboutViewController"
bundle:[NSBundle mainBundle]];
UITabBarItem *aboutItem = [[UITabBarItem alloc]initWithTitle:@"About" //3
image:[UIImage imageNamed:@"tag"]
tag:0];
[aboutViewController setTabBarItem:aboutItem];//4
NSMutableArray *currentItems = [NSMutableArray arrayWithArray:tabBarController.viewControllers];//5
[currentItems addObject:aboutViewController];//6
[tabBarController setViewControllers:currentItems animated:NO];//7
return YES;
}
注释:
- 获得对标签栏控制器的引用。
- 从XIB文件创建About视图控制器
- 为About场景创建一个标签栏选项,并设置合适的标题和图片
- 将About视图控制器的tabBarItem设置为新的标签栏选项。标签栏控制器会在设置标签栏选项的时候寻找该属性
- 基于那些由标签栏控制器进行管理的视图控制器,创建一个可变数组
- 将About场景添加到视图控制器数组的结尾。标签栏选项以数组中相同的顺序显示
- 在应用程序启动时,以不带动画效果的方式更新选项卡数组。如果该操作是在应用程序因用户行为而运行时发生,那么我们可能愿意以动画效果更新变化
好了运行程序,再次贴图:
今天的介绍就到这里咯