iOS Apprentice中文版-从0开始学iOS开发-第二十课

给待办事项分类

这个app的名称叫做Checklists是有原因的:它允许你拥有多条待办分类,目前为止,我们都只能添加待办条目,但是不久之后你就会拥有添加分类的功能,你可以先增加一些分类,比如会议,日程等分类,然后再向每个分类下增加具体的待办项目。

我们要做以下几件事情:

1、添加一个新的界面展示分类。

2、创建一个新的界面,可以使用户添加或者编辑新的分类

3、当你点击具体一个分类时,显示其中的待办事项

4、对分类进行保存和读取

两个新的界面意味着我们要两个新的视图控制器。

1、AllListsViewController展示用户所有的分类。

2、ListDetailViewController,使用户可以添加,编辑分类以及为分类增加一个图标

首先你要添加的是AllListsViewController。这也是这个app新的主界面。

当你完成后,app看起来会是这个样子:

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第1张图片
新的app主界面

这个界面和你之前创建的界面非常相似。它也是一个table view controller。

从现在开始,我会将这个新的主界面称为“分类”界面,而将之前的展示待办事项列表的界面称为“事项”界面,以示区别。

在工程导航器中右击Checklists分组(黄色文件夹图标的那个),然后选择New File,选择Cocoa Touch Class模版(iOS标签下的)。

然后按照下面的示例选择选项:

Class:AllListsViewController

Subclass of:UITableViewController

Also create XIB file:Uncheck this(不要勾选)

Language:Swift

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第2张图片
新建AllListsViewController文件

注意:确保Subclass of这一栏填写的是UITableViewController,而不是UIViewController。同时注意一下,Xcode会自动将AllListsViewController重命名为AllListsTableViewController,多了一个Table,你需要稍微修改一下。

点击Next,然后点击Create提交。

Xcode的table view controller模版中会预置一些代码,但是也许你用不到它们。模版只是把认为你需要的东西都提前列了出来,所以首先我们要把它们都删干净。

你还是先要自己造一点数据,让app能先跑起来。和你知道的一样,我每做一小步都会运行app测试一下,如果运行效果正常,那么就进入到下一步。

打开AllListsViewController.swift,删除掉numberOfSections(in)方法。没有这个方法的时候,列表就只会有一个分节。

将tableView(numberOfRowsInSection)修改为:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

接下来修改tableView(cellForRowAt),注意一下,这个方法在文件里有,只是被注释掉了,你可以把注释取消掉就可以了。并且修改为下面这个样子:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = makeCell(for: tableView)
        cell.textLabel!.text = "List \(indexPath.row)"
        return cell
    }

在ChecklistViewController中你是在界面建造器中设计cell单元,但是在AllListsViewController中,我们使用另外一种方式,用代码来设计cell单元。

你需要根据编译器的提示添加下面这个方法:

func makeCell(for tableView: UITableView) -> UITableViewCell {
        let cellIdentifier = "Cell"
        if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
            return cell
        } else {
            return UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
        }
    }

一会我会详细的讲这些代码的作用,但是现在你要知道你在这里也使用了tableView.dequeueReusableCell(withIdentifier)。如果它返回nil,那么就是说没有可重用的cell,这时你要使用UITableViewCell(style, reuseIdentifier)来新建一个。

把这段代码单独分离出来后会起到保持tableView(cellForRowAt)简洁的作用。

把AllListsViewController.swift中的其他注释部分全部删掉,它们没有任何作用,只会让代码看起来乱糟糟的。

最后一步就是添加新的view controller到故事模版上。

打开故事模版并且拖拽一个新的Table View Controller到画布上,把它放到离第一个navigation controller近的地方。

按住ctrl并且从第一个navigation controller拖拽到这个新的视图控制器:

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第3张图片

在弹出的菜单上选择Relationship Segue分节下的root view controller:

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第4张图片
选择root view controller

这样会把前期存在的navigation controller和ChecklistViewController之间的链接断开,这样一来,Checklists就不再是主界面了。

选择新的这个table view controller并且打开身份检查器,在Class中输入AllListsViewController。

双击这个新的视图控制器的导航栏,并且将它重命名为Checklists。

这样在Xcode的略缩面板中All Lists View Controller的视图控制器会被重命名为Checklists,这可能会使你有些困惑,因为我们已经有一个Checklists了,我们稍后会处理这个问题。

你可以整理一下故事模版,调整下新视图控制器的位置,让它们看起来美观点,把它们放到一排去。

就像我之前提到过的,你在这里不要为这个table view使用标准cell单元,如果你已经用了,那么非常不错,并且作为一个练习你可以之后重写代码来使用标准cell单元,但是这次我要为你展示一个新的方法来生成table view cells。

把All Lists View Controller中的空的prototype cell删掉,选定以后按delete键就可以了。

然后选定视图控制(黄色圆圈图标按钮的那个),然后按住ctrl键拖拽到Checklist View Controller中去,创建一个Show转场。

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第5张图片

这样就在从All Lists界面到Checklist界面见加入了一个推入的转换。同时也把右边的导航栏放回到Checklist界面。

双击右边的导航栏,修改标题为Name of the Checklist。这只是预置的文本,它可以帮你区分略缩面板中的各个视图控制器。

⚠️:略缩面板中不显示视图控制器对象的名称,而只是显示导航栏的文本,这一点Xcode需要改进,否则非常容易把人弄晕。
当我们说到All Lists View Controller时,就是指略缩面板中的Checklists Scene。
而Checklist View Controller现在则是略缩面板中的Name of the Checklist Scene。

注意一下,这个转场没有和任何按钮或者table view cell关联。

并且在All Lists界面上也没有任何东西给你点击,换而言之就是说你无法触发这个转场。这就意味着你要通过编程的方式来触发它。

选定新的转场,并且打开属性检查器,在identifier输入ShowChecklist。

这里你还可以看到这个转场的Kind(类型)为Show (e.g.Push),因为执行这个转场时,你正在将Checklist View Controller推到导航层的上面。

打开AllListsViewController.swift,添加tableView(didSelectRowAt)方法:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "ShowChecklist", sender: nil)
    }

回忆一下,这个table view委托方法是当用户点击某一行的时候被触发。

之前,点击某一行时,会自动触发一个转场,因为你将转场和cell单元链接起来了。然而,这个新的table view并没有使用cell单元,因此你需要手动执行转场。

这非常简单:只需要调用performSegue(withIdentifier,sender)就可以了。

运行app,它看起来应该是这个样子:

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第6张图片
左边是第一个界面,右边是点击某一行后进入的界面

点击某一行后我们非常熟悉的ChecklistViewController就滑动到屏幕中了。

你可以点击导航栏上左边的“Back”回到主界面上。现在你应该真正体会到导航控制器的强大了。

在All Lists界面中放入内容

你将要把Checklist View Controller中的大部分功能复制到All Lists界面中。

这里将会有一个➕号按钮用户添加新的分类,可以通过滑动删除某个分类,可以通过点击详细信息按钮来编辑某个分类。

当然,你还要将分类对象保存到一个plist文件中。

因为之前这些步骤我们已经详细讲解过了,所以这次我们会将的稍微快一点。

我们首先要创建一个数据模型来代表分类,就叫做Checklist好了,之前的代表具体某条待办事项的数据模型叫做ChecklistItem。

用Cocoa Touch Class模版添加一个新的文件,将其命名为Checklist并且设置为NSObject的子类。

就像ChecklistItem一样,你需要把Checklist作为NSObject的子类,因为NSCoder系统在存储和读取时,对象必须是这种类型。

给Checklist.swift一个叫做name的属性:

import UIKit

class Checklist: NSObject {
    var name = ""
}

接下来,你需要一个数组来保存AllLIstsViewController的Checklist对象。

在AllListsViewController.swift中添加一个新的实例变量。

var lists: [Checklist]

这个数组就用于存储Checklist对象。

⚠️:你也可以这样声明数组:
var lists: Array
在Swift代码中你会见到这两种声明方式,它们的作用是一样的。

你可以给这个新的数组添加一点测试数据,可以通过init?(coder)来实现这一目的。记住当UIKit从故事模版中读取视图控制器时会自动调用这个方法。

打开AllListsViewController.swift,像下面这样就可以实现了(先不要急着动手去做,先阅读一遍,当需要你敲代码的时候,我会通知你的)

required init?(coder aDecoder: NSCoder) {
  // 1
  lists = [Checklist]()
  // 2
  super.init(coder: aDecoder)
// 3
  var list = Checklist()
list.name = "Birthdays"
  lists.append(list)
// 4
  list = Checklist()
  list.name = "Groceries"
  lists.append(list)
  list = Checklist()
  list.name = "Cool Apps"
  lists.append(list)
  list = Checklist()
  list.name = "To Do"
  lists.append(list)
}

这和你在ChecklistViewController中添加测试数据的方法非常相似。下面是每一步的讲解。

1、给lists变量一个值。你也可以写成lists= Array(),我比较喜欢方括号的那个版本。

2、调用init?(coder)父类的初始化方法。没有这一步,就不能从故事模版中读取出这个视图来。但是你也不用太担心,如果你真的忘了这个步骤,Xcode会提醒你的。

创建一个新的Checklist对象,给它一个名称,并且将它添加到数组中。

4、重复创建多个Checklist对象。因为list是一个变量,所以你可以复用它。

注意一下,你每次创建一个新的Checklist对象,都重复了同样的两个步骤:

 list = Checklist()
list.name = "Name of the check

看起来每一个你创建的Checklist对象都有一个名称。你可以通过自己写一个init方法,将name作为一个参数,然后你就可以将这两行合并为一行了,就像下面这样:

list = Checklist(name: "Name of the checklist")

打开Checklist.swift并且添加新的init方法:

init(name: String) {
        self.name = name
        super.init()
    }

这个初始化用了一个参数name,并且将它传递给实例变量name。

因为参数名称和实例变量都叫做name,所以你使用self.name来引用实例变量。

如果你像下面这样写代码:

init(name: String) {
        name = name
        super.init()
    }

编译器就会死给你看,因为它分不清那个name是参数,而哪个name是实例变量了。

为了消除歧义,你在实例变量name前加了self.回忆一下,self引用你当前所处的对象,所以self.name就代表Checklist中的实例变量name。

回到AllListsViewController.swift,然后添加init?(coder)方法,这次你要动手去做了。

required init?(coder aDecoder: NSCoder) {
        lists = [Checklist]()
        
        super.init(coder: aDecoder)
        
        var list = Checklist(name: "Birthdays")
        lists.append(list)
        
        list = Checklist(name: "Groceries")
        lists.append(list)
        
        list = Checklist(name: "Cool Apps")
        lists.append(list)
        
        list = Checklist(name: "To Do")
        lists.append(list)
    }

这比最初我展示给你看的那一版简单了许多,并且它保证了新的Checklist对象的name属性总是有值的。

注意一下,你不会写成下面这个样子:

var list = Checklist.init(name: "Birthdays")

虽然方法的名称叫做init,但是它不是标准方法。你在使用初始化方法的时候只需要像下面这样写:

var object = ObjectName(parameter1: value1, parameter2: value2, . . .)

根据你指定的参数,Swift会自动找到相应的init方法。

明白了吗?我们进入到下一步吧。

将tableView(numberOfRowsInSection)修改为下面这个样子:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return lists.count
    }

然后修改tableView(cellForRowAt),来在cell中填进刚才的数据:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = makeCell(for: tableView)
        
        let checklist = lists[indexPath.row]
        cell.textLabel!.text = checklist.name
        cell.accessoryType = .detailDisclosureButton

运行app,看起来会是这个样子:

iOS Apprentice中文版-从0开始学iOS开发-第二十课_第7张图片

这个界面还有很多剩下的工作要做,这里仅仅是一个开始。

你可能感兴趣的:(iOS Apprentice中文版-从0开始学iOS开发-第二十课)