給待辦事項(xiàng)分類
這個(gè)app的名稱叫做Checklists是有原因的:它允許你擁有多條待辦分類,目前為止,我們都只能添加待辦條目,但是不久之后你就會(huì)擁有添加分類的功能,你可以先增加一些分類,比如會(huì)議,日程等分類,然后再向每個(gè)分類下增加具體的待辦項(xiàng)目。
我們要做以下幾件事情:
1、添加一個(gè)新的界面展示分類。
2、創(chuàng)建一個(gè)新的界面,可以使用戶添加或者編輯新的分類
3、當(dāng)你點(diǎn)擊具體一個(gè)分類時(shí),顯示其中的待辦事項(xiàng)
4、對(duì)分類進(jìn)行保存和讀取
兩個(gè)新的界面意味著我們要兩個(gè)新的視圖控制器。
1、AllListsViewController展示用戶所有的分類。
2、ListDetailViewController,使用戶可以添加,編輯分類以及為分類增加一個(gè)圖標(biāo)
首先你要添加的是AllListsViewController。這也是這個(gè)app新的主界面。
當(dāng)你完成后,app看起來會(huì)是這個(gè)樣子:

這個(gè)界面和你之前創(chuàng)建的界面非常相似。它也是一個(gè)table view controller。
從現(xiàn)在開始,我會(huì)將這個(gè)新的主界面稱為“分類”界面,而將之前的展示待辦事項(xiàng)列表的界面稱為“事項(xiàng)”界面,以示區(qū)別。
在工程導(dǎo)航器中右擊Checklists分組(黃色文件夾圖標(biāo)的那個(gè)),然后選擇New File,選擇Cocoa Touch Class模版(iOS標(biāo)簽下的)。
然后按照下面的示例選擇選項(xiàng):
Class:AllListsViewController
Subclass of:UITableViewController
Also create XIB file:Uncheck this(不要勾選)
Language:Swift

注意:確保Subclass of這一欄填寫的是UITableViewController,而不是UIViewController。同時(shí)注意一下,Xcode會(huì)自動(dòng)將AllListsViewController重命名為AllListsTableViewController,多了一個(gè)Table,你需要稍微修改一下。
點(diǎn)擊Next,然后點(diǎn)擊Create提交。
Xcode的table view controller模版中會(huì)預(yù)置一些代碼,但是也許你用不到它們。模版只是把認(rèn)為你需要的東西都提前列了出來,所以首先我們要把它們都刪干凈。
你還是先要自己造一點(diǎn)數(shù)據(jù),讓app能先跑起來。和你知道的一樣,我每做一小步都會(huì)運(yùn)行app測(cè)試一下,如果運(yùn)行效果正常,那么就進(jìn)入到下一步。
打開AllListsViewController.swift,刪除掉numberOfSections(in)方法。沒有這個(gè)方法的時(shí)候,列表就只會(huì)有一個(gè)分節(jié)。
將tableView(numberOfRowsInSection)修改為:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
接下來修改tableView(cellForRowAt),注意一下,這個(gè)方法在文件里有,只是被注釋掉了,你可以把注釋取消掉就可以了。并且修改為下面這個(gè)樣子:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = makeCell(for: tableView)
cell.textLabel!.text = "List \(indexPath.row)"
return cell
}
在ChecklistViewController中你是在界面建造器中設(shè)計(jì)cell單元,但是在AllListsViewController中,我們使用另外一種方式,用代碼來設(shè)計(jì)cell單元。
你需要根據(jù)編譯器的提示添加下面這個(gè)方法:
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)
}
}
一會(huì)我會(huì)詳細(xì)的講這些代碼的作用,但是現(xiàn)在你要知道你在這里也使用了tableView.dequeueReusableCell(withIdentifier)。如果它返回nil,那么就是說沒有可重用的cell,這時(shí)你要使用UITableViewCell(style, reuseIdentifier)來新建一個(gè)。
把這段代碼單獨(dú)分離出來后會(huì)起到保持tableView(cellForRowAt)簡(jiǎn)潔的作用。
把AllListsViewController.swift中的其他注釋部分全部刪掉,它們沒有任何作用,只會(huì)讓代碼看起來亂糟糟的。
最后一步就是添加新的view controller到故事模版上。
打開故事模版并且拖拽一個(gè)新的Table View Controller到畫布上,把它放到離第一個(gè)navigation controller近的地方。
按住ctrl并且從第一個(gè)navigation controller拖拽到這個(gè)新的視圖控制器:

在彈出的菜單上選擇Relationship Segue分節(jié)下的root view controller:

這樣會(huì)把前期存在的navigation controller和ChecklistViewController之間的鏈接斷開,這樣一來,Checklists就不再是主界面了。
選擇新的這個(gè)table view controller并且打開身份檢查器,在Class中輸入AllListsViewController。
雙擊這個(gè)新的視圖控制器的導(dǎo)航欄,并且將它重命名為Checklists。
這樣在Xcode的略縮面板中All Lists View Controller的視圖控制器會(huì)被重命名為Checklists,這可能會(huì)使你有些困惑,因?yàn)槲覀円呀?jīng)有一個(gè)Checklists了,我們稍后會(huì)處理這個(gè)問題。
你可以整理一下故事模版,調(diào)整下新視圖控制器的位置,讓它們看起來美觀點(diǎn),把它們放到一排去。
就像我之前提到過的,你在這里不要為這個(gè)table view使用標(biāo)準(zhǔn)cell單元,如果你已經(jīng)用了,那么非常不錯(cuò),并且作為一個(gè)練習(xí)你可以之后重寫代碼來使用標(biāo)準(zhǔn)cell單元,但是這次我要為你展示一個(gè)新的方法來生成table view cells。
把All Lists View Controller中的空的prototype cell刪掉,選定以后按delete鍵就可以了。
然后選定視圖控制(黃色圓圈圖標(biāo)按鈕的那個(gè)),然后按住ctrl鍵拖拽到Checklist View Controller中去,創(chuàng)建一個(gè)Show轉(zhuǎn)場(chǎng)。

這樣就在從All Lists界面到Checklist界面見加入了一個(gè)推入的轉(zhuǎn)換。同時(shí)也把右邊的導(dǎo)航欄放回到Checklist界面。
雙擊右邊的導(dǎo)航欄,修改標(biāo)題為Name of the Checklist。這只是預(yù)置的文本,它可以幫你區(qū)分略縮面板中的各個(gè)視圖控制器。
??:略縮面板中不顯示視圖控制器對(duì)象的名稱,而只是顯示導(dǎo)航欄的文本,這一點(diǎn)Xcode需要改進(jìn),否則非常容易把人弄暈。
當(dāng)我們說到All Lists View Controller時(shí),就是指略縮面板中的Checklists Scene。
而Checklist View Controller現(xiàn)在則是略縮面板中的Name of the Checklist Scene。
注意一下,這個(gè)轉(zhuǎn)場(chǎng)沒有和任何按鈕或者table view cell關(guān)聯(lián)。
并且在All Lists界面上也沒有任何東西給你點(diǎn)擊,換而言之就是說你無法觸發(fā)這個(gè)轉(zhuǎn)場(chǎng)。這就意味著你要通過編程的方式來觸發(fā)它。
選定新的轉(zhuǎn)場(chǎng),并且打開屬性檢查器,在identifier輸入ShowChecklist。
這里你還可以看到這個(gè)轉(zhuǎn)場(chǎng)的Kind(類型)為Show (e.g.Push),因?yàn)閳?zhí)行這個(gè)轉(zhuǎn)場(chǎng)時(shí),你正在將Checklist View Controller推到導(dǎo)航層的上面。
打開AllListsViewController.swift,添加tableView(didSelectRowAt)方法:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "ShowChecklist", sender: nil)
}
回憶一下,這個(gè)table view委托方法是當(dāng)用戶點(diǎn)擊某一行的時(shí)候被觸發(fā)。
之前,點(diǎn)擊某一行時(shí),會(huì)自動(dòng)觸發(fā)一個(gè)轉(zhuǎn)場(chǎng),因?yàn)槟銓⑥D(zhuǎn)場(chǎng)和cell單元鏈接起來了。然而,這個(gè)新的table view并沒有使用cell單元,因此你需要手動(dòng)執(zhí)行轉(zhuǎn)場(chǎng)。
這非常簡(jiǎn)單:只需要調(diào)用performSegue(withIdentifier,sender)就可以了。
運(yùn)行app,它看起來應(yīng)該是這個(gè)樣子:

點(diǎn)擊某一行后我們非常熟悉的ChecklistViewController就滑動(dòng)到屏幕中了。
你可以點(diǎn)擊導(dǎo)航欄上左邊的“Back”回到主界面上?,F(xiàn)在你應(yīng)該真正體會(huì)到導(dǎo)航控制器的強(qiáng)大了。
在All Lists界面中放入內(nèi)容
你將要把Checklist View Controller中的大部分功能復(fù)制到All Lists界面中。
這里將會(huì)有一個(gè)?號(hào)按鈕用戶添加新的分類,可以通過滑動(dòng)刪除某個(gè)分類,可以通過點(diǎn)擊詳細(xì)信息按鈕來編輯某個(gè)分類。
當(dāng)然,你還要將分類對(duì)象保存到一個(gè)plist文件中。
因?yàn)橹斑@些步驟我們已經(jīng)詳細(xì)講解過了,所以這次我們會(huì)將的稍微快一點(diǎn)。
我們首先要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)模型來代表分類,就叫做Checklist好了,之前的代表具體某條待辦事項(xiàng)的數(shù)據(jù)模型叫做ChecklistItem。
用Cocoa Touch Class模版添加一個(gè)新的文件,將其命名為Checklist并且設(shè)置為NSObject的子類。
就像ChecklistItem一樣,你需要把Checklist作為NSObject的子類,因?yàn)镹SCoder系統(tǒng)在存儲(chǔ)和讀取時(shí),對(duì)象必須是這種類型。
給Checklist.swift一個(gè)叫做name的屬性:
import UIKit
class Checklist: NSObject {
var name = ""
}
接下來,你需要一個(gè)數(shù)組來保存AllLIstsViewController的Checklist對(duì)象。
在AllListsViewController.swift中添加一個(gè)新的實(shí)例變量。
var lists: [Checklist]
這個(gè)數(shù)組就用于存儲(chǔ)Checklist對(duì)象。
??:你也可以這樣聲明數(shù)組:
var lists: Array<Checklist>
在Swift代碼中你會(huì)見到這兩種聲明方式,它們的作用是一樣的。
你可以給這個(gè)新的數(shù)組添加一點(diǎn)測(cè)試數(shù)據(jù),可以通過init?(coder)來實(shí)現(xiàn)這一目的。記住當(dāng)UIKit從故事模版中讀取視圖控制器時(shí)會(huì)自動(dòng)調(diào)用這個(gè)方法。
打開AllListsViewController.swift,像下面這樣就可以實(shí)現(xiàn)了(先不要急著動(dòng)手去做,先閱讀一遍,當(dāng)需要你敲代碼的時(shí)候,我會(huì)通知你的)
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中添加測(cè)試數(shù)據(jù)的方法非常相似。下面是每一步的講解。
1、給lists變量一個(gè)值。你也可以寫成lists= Array<Checklist>(),我比較喜歡方括號(hào)的那個(gè)版本。
2、調(diào)用init?(coder)父類的初始化方法。沒有這一步,就不能從故事模版中讀取出這個(gè)視圖來。但是你也不用太擔(dān)心,如果你真的忘了這個(gè)步驟,Xcode會(huì)提醒你的。
創(chuàng)建一個(gè)新的Checklist對(duì)象,給它一個(gè)名稱,并且將它添加到數(shù)組中。
4、重復(fù)創(chuàng)建多個(gè)Checklist對(duì)象。因?yàn)閘ist是一個(gè)變量,所以你可以復(fù)用它。
注意一下,你每次創(chuàng)建一個(gè)新的Checklist對(duì)象,都重復(fù)了同樣的兩個(gè)步驟:
list = Checklist()
list.name = "Name of the check
看起來每一個(gè)你創(chuàng)建的Checklist對(duì)象都有一個(gè)名稱。你可以通過自己寫一個(gè)init方法,將name作為一個(gè)參數(shù),然后你就可以將這兩行合并為一行了,就像下面這樣:
list = Checklist(name: "Name of the checklist")
打開Checklist.swift并且添加新的init方法:
init(name: String) {
self.name = name
super.init()
}
這個(gè)初始化用了一個(gè)參數(shù)name,并且將它傳遞給實(shí)例變量name。
因?yàn)閰?shù)名稱和實(shí)例變量都叫做name,所以你使用self.name來引用實(shí)例變量。
如果你像下面這樣寫代碼:
init(name: String) {
name = name
super.init()
}
編譯器就會(huì)死給你看,因?yàn)樗植磺迥莻€(gè)name是參數(shù),而哪個(gè)name是實(shí)例變量了。
為了消除歧義,你在實(shí)例變量name前加了self.回憶一下,self引用你當(dāng)前所處的對(duì)象,所以self.name就代表Checklist中的實(shí)例變量name。
回到AllListsViewController.swift,然后添加init?(coder)方法,這次你要?jiǎng)邮秩プ隽恕?/p>
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)
}
這比最初我展示給你看的那一版簡(jiǎn)單了許多,并且它保證了新的Checklist對(duì)象的name屬性總是有值的。
注意一下,你不會(huì)寫成下面這個(gè)樣子:
var list = Checklist.init(name: "Birthdays")
雖然方法的名稱叫做init,但是它不是標(biāo)準(zhǔn)方法。你在使用初始化方法的時(shí)候只需要像下面這樣寫:
var object = ObjectName(parameter1: value1, parameter2: value2, . . .)
根據(jù)你指定的參數(shù),Swift會(huì)自動(dòng)找到相應(yīng)的init方法。
明白了嗎?我們進(jìn)入到下一步吧。
將tableView(numberOfRowsInSection)修改為下面這個(gè)樣子:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
然后修改tableView(cellForRowAt),來在cell中填進(jìn)剛才的數(shù)據(jù):
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
運(yùn)行app,看起來會(huì)是這個(gè)樣子:

這個(gè)界面還有很多剩下的工作要做,這里僅僅是一個(gè)開始。