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

给待办事项分类

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

我们要做以下几件事情:

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

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

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

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

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

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

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

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

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

新的app主界面

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

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

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

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

Class:AllListsViewController

Subclass of:UITableViewController

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

Language:Swift

新建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拖拽到这个新的视图控制器:

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

选择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转场。

这样就在从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,它看起来应该是这个样子:

左边是第一个界面,右边是点击某一行后进入的界面

点击某一行后我们非常熟悉的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<Checklist>
在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<Checklist>(),我比较喜欢方括号的那个版本。

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,看起来会是这个样子:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容