第五章:创建多个界面的Apps
在这一章节中,你讲学会如何在一个App里如果管理多个界面之间的跳转,还将学会如何创建一个滚动列表。 大多数的App都有多个界面和至少一个滚动列表。这章将会帮你构建知识基础,有了这些知识储备,你离着实现发布App这个目标就更近了。
视图控制器(View Controllers)
视图控制器是MVC(Modl-View-Controller)模式的逻辑部分。可以去第一章快速复习一下MVC的知识。视图控制器就是其字面的意思,这个控制器能够控制某个视图,不用过度思考。视图能够展示信息,也能够接收用户的输入。但是视图不能做决定,视图控制器来做决定。
UIViewController
苹果极力将MVC模式推荐给开发者,非常重视MVC,并且编写了自己的控制器叫做UIViewController,UIViewController是UIKit的一部分。UIKit是众多能够制作交互界面元素的类,如果你在某个类的开头是UI,那么这个类属于UIkit。UIViewController已经帮你处理了很多编程时的脏活累活和体力活,它有开箱即用的view property(视图属性),这个视图属性被连接到一个视图文件,大多数情况下,是一个storyboard文件。 UIviewController也已经为你提前写好了一些方法。一些常见的事件,诸如视图的载入和视图的消失都已经写好了,你可以把这些方法放到你自己的代码中使用。
Page 135
总之,UIViewController提供了一些你需要的方法和属性,这样使UIViewController成为一个完美的父类,可以作为模板生成你自己的类。 例如,通常我们把UIViewController子类化,为了能够运行这个子类,在第一次运行之前,我们还需要添加一些代码进去:
class mySubController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad() // Do any addition setup after loading the view
}
}
看一下mySubController类中的viewDidLoad()
方法,override关键词告诉Xcode你要改变UIViewcontroller中默认的viewDidLoad()
方法,func关键词声明了这个方法。viewDidLoad是你想从UIViewController中重写的方法的名字。viewDidLoad()
方法没有参数值,最后你要加一对大括号,大括号表示方法的开始和结束位置。
不管什么时候你想重写一个方法,代码的第一行要调用super,super这个关键词是用来表示目前这个类的父类。在这个例子中,父类就是UIViewController。调用super是非常重要的一步,如果不调用super,在你的代码被执行之前,viewDidLoad()
方法是不会做什么事情的,就好像你要建房子,却没有先打地基。所以用super.viewDidLoad()
来打地基,打好地基后,你就可以增加你自己的代码:
class mySubController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any addition setup after loading the view
println("The View Has Loaded")
}
}
printlin方法调用后将会在Debugger中打印出一段信息"The View Has Loaded"。在编程时,记着给每一个视图控制器都配置一个视图和控制器,这是个好习惯。
UINavigationController
直到现在,你编写的App都只有一个界面。然而,现在iOS平台上知名的App都有多个界面,能够从左过渡到右边。例如,iPhone上的邮件App,当你点击一个邮件时,会滑动到一个新的界面。
Page 136 | Chapter 5 : Building Multiscreen Apps
邮件App也有返回按钮。这种从左到右的过渡效果是navigation controller创建的。
一个UINavigationController可以在数组中支持多个UIViewController。记着,数组是集合类型,能够按照某个顺序存储多个元素。导航控制器(navigation controller)的使用的排序机制叫做堆栈(stack),按照先进后出的原则排列元素。stack有些像是登机,如果你第一个登上飞机,走到飞机后面坐在靠窗的位置,那么在下飞机时,你是最后一个离开飞机的。这就是先进后出原则在工作时的一个范例。 假设你第一个登上飞机,走到飞机后面坐在靠窗的位置,接着一个男性乘客登机坐在了你旁边的位置,接着一位女性乘客登机,坐在了靠近走廊的位置。那么当离开飞机时,先是这位女乘客立刻飞机,接着是男乘客,最后才是你。堆栈按照相反的顺序加载和卸载,加载的时候是由下到上,卸载的时候是由上到下。 为了给navigation controller增加view controller,你首页要把view controller push推到navigation controller的上面,push就相当于第一个人正在登机。The item goes as far toward the bottom as possible, but does not pass the itemsinserted before it. (这句话不会翻译,静等高手)。堆栈中最上面的view controller将被展示给用户。为了能给用户展示另外一个界面,就把另外一个界面push到当前的界面。 为了去掉当前的界面,我们使用navigation controller中的pop功能。(push和pop在ios9中已经被弃用了)pop是指从堆栈中移除最上面的元素。在这个例子中,pop移除的是当前展示的界面,然后回到之前的界面。pop相当于头等舱的乘客,最后一个登机,第一个离开飞机。 当一个view在navigation controller中展示时,会在这个view的顶部增加一个navigation bar。这个navigation bar有三个主要的部分:左边的UIBarButtonItem,中间的titleView,然后是右边的UIBarButtonItem,UIBarButtonItem就像是一个典型的button,但是它是在UINavigationBar中,UINavigationBar这个类用来生成navigation controller中的navigation bar,当另外一个界面展示在navigation controller中时,左边的UIBarButtonItem就会自动变成一个返回按钮。titleView展示UIViewController的title属性值,这里关键记着,title属性值是由view controller定义的,但是是在navigation controller中显示。在view controller中设置title属性非常简单:
title = " Countries"
每一个view controller都有title属性,因为UIViewController定义了这个属性。返回按钮也会显示要返回的界面的名字。
View Controller | Page 137
最后,右边的UIBarButtonItem是可选的,右边这个位置可以放置一个主要或者次要的行为。例如设置按钮可以放在App的右上角。
Table View
滚动视图是iOS Apps中最常见的用户界面。例如,iPhone的设置,里面就有一个目录滚动视图,当用户点击某个选项时,会跳到新的界面显示更详细的信息。这个效果是结合了navigation controller和table views来实现的。navigation controller控制着目前显示哪个界面,而table views则显示滚动视图内容。
table view有几个关键部分组成,滚动视图中的每一行叫做cell,cell是用了展示table view中每行的内容。table view可以有很多个cell,多个cell组成section(组)。sections是用来把行区分成不同的部分。在iPhone设置,就是用不同的section把目录分开,像是通知中心,控制中心,个人隐私。每个table view都有header和footer,header是在cell上面,footer在cell下面。
最后,一个table view有两种style(风格)。默认风格是Plain,一个紧挨着一个列出每一行,另外一个风格Grouped,是把一组一组在一起,不同的组之间用空隙间隔。
Delegation
很多的体育网站或者是App都会提供一个提醒的服务,当你喜欢的球队得分时,就会往你的手机或者邮箱中发送提醒的消息,比如不管什么时候旧金山巨人队赢了全垒打,你都会收到震动提醒。 类似的概念也应用在了控制器的提醒功能上,App内部发生某个事件时,就会发出提醒。为某个事件订阅或者接收提醒的过程叫做delegation(委托)。委托一个delegate接收所有的通知。然而,delegate不只是只接受通知,大多数情况下,delegate也必须回应这些通知。 例如,你要管理一个公司,你告诉保安处监视休息室,不管什么时候休息室的人数超过3个,就让保安敲你的门。敲门就是一个通知,在每次收到通知后,你可以选择如何处理这些通知。
当一个table view第一次创建时,需要回答一系列的问题后才能使用。table view让delegate来接收问题并提供答案。
Page 138 | Chapter 5 : Building Multiscreen Apps
例如,table view会问delegate要创建多少行,delegate必须用个具体的数字或者整型变量来回答这个问题。
实际上可以为每个需要知道的信息重写方法就能回答一系列的问题。例如,问要有多少行,你需要重新numberOfRowsInSection
方法:
override func tableView ( tableView: UITableView, numberOfRowsInSection section: Int ) -> Int {
//Return the number of rows in the section
return 10
}
如何看不懂方法名字,也没有关系,关注方法里面的代码就可以了。在这个例子中,numberOfRowsInSection
方法返回了一个整型,返回的是10,这样table view就会有10行。这就是众多必须回答的问题之一,回答完这些问题才能创建一个table view。
所有的问题都打包在一个协议(protocal)里。协议(protocal)以一种特定的方式做事。例如,机场的协议就是要检查你的行李,通过安检,登机。这每一个步骤,你都要提供信息,如目的地是哪里。关键是你要完全同意协议中要求的事项。在编程时,这就叫做conforming(遵从协议),表示你同意回应协议中所有需要回应的方法。
UITableViewController
UIViewController
同样的,UITableViewController也是已经为你创建table view做了大部分的脏活累活体力活。UITableViewController会自动创建一个table view,然后设置tableView
属性,同时也需要委托自己获取所有需要的delegate方法。
UITableViewDataSource
UITableView的delegate协议有三个必须要写的方法,叫做UITableViewDataSource。这个协议包括组的数量,美组中行的数量,以及cell如何展现。
Delegation | Page 139
第一个方法是numberOfSectionsInTableView(_:)
,这个方法需要一个整型值,来作为table view中section组的数量。重写这个方法非常简单:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
//#warning Potentially incomplete method implementation.
//return the number of sections.
return 0
}
注意到return那行目前是零,这意味着这个table view中没有组。苹果公司增加了一个警告注释,说如果组的个数是零,那么就不会显示行,组包含行cell,没有了组section,行cell也就不会被显示出来:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
//return the number of sections.
return 1
}
第二个方法是tableView(_:numberOfRowsInSection:)
,这个方法需要一个整型,决定了某个组里具体有多少行:
override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
//#warning Incomplete method implementation
// Return the number of rows in the section
return 0
}
注意到return后面是零,这意味着每个组里都没有行,所以苹果公司增加了一个警告注释,让你重写这个方法:
override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
//#warning Incomplete method implementation
// Return the number of rows in the section
return 5
}
最后第三个方法是tableView(:cellForRowAtIndexPath:)
,这个是和行cell有关。这个方法里有个参数值叫indexPath
,是一个NSIndexPath。NSIndexPath有两个属性。
Page 140 | Chapter 5 : Building Multiscreen Apps
section组属性的索引是当前组,cell行属性的索引是当前行。记着索引是从零开始计数的:
- 第一组第一行的索引NSIndexPath是0,0。
- 第一组第四行的索引NSIndexPath是0,3。
- 第三组第一行的索引NSIndexPath是2,0。
可以用点语法调用section和row属性:
var currentRow = indexPath.row
var currentSection = indexPath.section
tableView(:cellForRowAtIndexPath:)
这个方法一开始会让有些你灰心丧气,不过不用担心,因为过一会就会明白了。先值关注方法里面的代码,不要害怕犯错误:
override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell =tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
return cell
}
Cell reuse(cell 重复使用)
第一行先创建了一个名为cell的常量。cell等同于一个table view的一个离队行(dequeued cells)。一个离队的cell是一个不再使用的cell。就和真实的世界一样,循环利用资源非常重要。当table view有很多行,受屏幕限制能一次显示出来的行数是有限的,这些还没有显示出来的行实际上还没有创建出来。当这行滑出屏幕后,就进入了回收箱。在新的行创建之前,回收箱会检查一下有没有可回收利用的行。正是因为cell的可重复使用,在使用之前先删除之前的信息,就变得格外重要了。
通过tableView
变量来访问table view,dequeueReusableCellWithIdentifier
方法使用回收来的cell或者创建新的cell。dequeueReusableCellWithIdentifier
方法有两个参数值:identifier
,String类型,确认具体哪一行,IndexPath
是NSIndexPath类型,标识具体位置。
cellForRowAtIndexPath方法用override开头,因为这个方法是继承自UITableViewController。有了override,可以重写cellForRowAtIndexPath方法了。
Delegation | Page 141
func关键词表示这是一个方法。第一个tableView是这个方法名字的一部分。括号中有这个方法需要的参数,第一个参数名字是tableView,是一个UITableView。cellForRowAtIndexPath是这个方法的名字,接下来的参数名字是indexPath,是一个NSIndexPath。最后,这个方法需要一个UITableViewCell作为返回值。
一旦你成功创建cell,接下来就是为cell设置属性来展示你的数据。 UITableViewCell有5类主要属性: 1.textLabel,属于UILabel,展示主要信息; 2.detailTextLabel,属于UILabel,展示副标题,这个label不总是出现的; 3.imageView,属于UIImageView,在cell的左边显示图片; 4.accessoryView,显示一个大于号; 5.contentView,空白cell,可自定义。
UITableViewCell有4种风格(style),上面的5个属性在不同的风格下有可能被隐藏或者位置变化: 1.Default 左对齐的text label,可选的imageView,没有detailTextLabel 2.Value1 左对齐的黑色字体的text label,一个更小的蓝色字体右对齐 3.Value2 左侧有个右对齐的蓝色字体的text label,右侧有个左对齐的黑色字体的text label 4.Subtitle 左侧左对齐的黑色字体的text label,接着是一个字体更小的左对齐灰色text label
在这一章节中,你已经学会了在一个APP里管理多个界面,还学会了如何创建一个滚动视图,你的知识库正在充盈,现在是时候把刚刚学会的东西应用一下了,开始我们的Passport练习吧。
Page 142 | Chapter 5 : Building Multiscreen Apps