來(lái)個(gè)簡(jiǎn)短的插曲—關(guān)于函數(shù)編程
Swift首先是一種面向?qū)ο缶幊陶Z(yǔ)言,但是實(shí)際上存在一種其他風(fēng)格的編程方式并且在最近幾年也很流行,那就是函數(shù)編程。
“函數(shù)”這個(gè)術(shù)語(yǔ)的意思就是,可以純粹的通過數(shù)學(xué)函數(shù)來(lái)傳送并且改變數(shù)據(jù)。
不同于Swift中的函數(shù)或者方法,這些數(shù)據(jù)函數(shù)不允許出現(xiàn)所謂的“側(cè)面效應(yīng)”,對(duì)于任何給定的輸入,這個(gè)函數(shù)總是會(huì)產(chǎn)生一個(gè)同樣的輸出。方法則沒這么嚴(yán)格。
雖然Swift不是一種函數(shù)語(yǔ)言,它還是可以讓你在app使用一些函數(shù)編程的技術(shù)。它們會(huì)使你的代碼變的非常簡(jiǎn)潔。
作為例子,我們?cè)賮?lái)看一看countUncheckedItems():
func countUncheckedItems() -> Int {
var count = 0
for item in items where !item.checked {
count += 1 }
return count
}
這是相當(dāng)簡(jiǎn)單的一小段代碼。但是實(shí)際上你可以把它們濃縮為一行:
func countUncheckedItems() -> Int {
return items.reduce(0) { cnt, item in cnt + (item.checked ? 0 : 1) }
}
reduce()是一個(gè)函數(shù),作用為對(duì)每一個(gè)條目執(zhí)行花括號(hào)內(nèi)的代碼。最初變量cnt包含的值為0,但是每次執(zhí)行后它都會(huì)增加0或者1,這取決于條目是否checked。
當(dāng)reduce()結(jié)束后,它返回為被打勾的數(shù)量的總和。
你不用記住或者理解這些,但是了解一下Swift可以允許你執(zhí)行這種簡(jiǎn)潔的算法,是非常棒的。
對(duì)列表進(jìn)行排序
對(duì)列表常見的另一個(gè)操作就是以某種規(guī)則排序。
讓我們來(lái)以名稱為順序來(lái)對(duì)列表進(jìn)行排序。目前當(dāng)你添加一個(gè)Checklist的時(shí)候,新增的條目總是出現(xiàn)在最后一條,無(wú)論它的首寫字母在字母表中的順序如何。
在我們了解如何對(duì)數(shù)組進(jìn)行排序前,讓我們想想什么時(shí)候需要執(zhí)行排序:
1、新增的時(shí)候
2、重命名的時(shí)候
沒有必要在刪除掉時(shí)候進(jìn)行重新排序,因?yàn)閯h除不會(huì)引起序列的變化。
目前這兩種操作都是由AllListsViewController中的 “didFinishAdding” 和“didFinishEditing”來(lái)執(zhí)行。
將這兩個(gè)方改動(dòng)一下:
func listDetailViewController(_ controller: ListDetailViewController, didFinishAdding checklist: Checklist) {
dataModel.lists.append(checklist)
dataModel.sortChecklists()
tableView.reloadData()
dismiss(animated: true, completion: nil)
}
func listDetailViewController(_ controller: ListDetailViewController, didFinishEditing checklist: Checklist) {
dataModel.sortChecklists()
tableView.reloadData()
dismiss(animated: true, completion: nil)
}
在這兩個(gè)方法中你刪掉了不少東西,因?yàn)楝F(xiàn)在你總是在table view上使用reloadData()。
它再也不用手動(dòng)插入一個(gè)新行了,或者更新cell的textLabel。而是簡(jiǎn)單的通過調(diào)用tableView.reloadData()來(lái)刷新整個(gè)列表的內(nèi)容。
你再一次使用了全量更新,因?yàn)槲覀冞@個(gè)app中不太可能會(huì)出現(xiàn)大量數(shù)據(jù)。如果我們的列表中會(huì)出現(xiàn)上千行,那么用更加科學(xué)的方法才會(huì)顯得比較有必要。(對(duì)于大量數(shù)據(jù),你可以定位新增或者重命名的是哪一行,然后僅僅對(duì)這一行進(jìn)行更新)
DataModel中的sortChecklists()是一個(gè)新的方法,你還沒有添加它。在這之前,我們來(lái)討論下排序是如何實(shí)現(xiàn)的。
當(dāng)你對(duì)一個(gè)列表中的條目進(jìn)行排序時(shí),app會(huì)逐條比較來(lái)找出每一條合適的位置,但是比較兩個(gè)Checklist對(duì)象是什么意思呢?
在我們的app中,很顯然,我們想要根據(jù)名稱進(jìn)行排序,但是我們需要一些方法來(lái)告訴app我們的意圖。
在DataModel.swift中添加以下方法:
func sortChecklists() {
lists.sort(by: {
checklist1, checklist2 in
return checklist1.name.localizedStandardCompare(checklist2.name) == .orderedAscending
})
}
這里你告訴lists數(shù)組Checklists的內(nèi)容要以某種準(zhǔn)則排序。
這個(gè)準(zhǔn)則由閉包提供。花括號(hào)內(nèi)就是排序的代碼;它們以閉包的形式存在:
lists.sort(by: { /* 這里就是排序代碼 */ })
在Bull's Eye這個(gè)課程中,我們簡(jiǎn)單的介紹過一次閉包。它們包裹一段代碼到一個(gè)匿名的,內(nèi)聯(lián)的方法中。
這個(gè)閉包的作用是判斷一個(gè)Checklist對(duì)象是否應(yīng)該排在另一個(gè)之前,基于你提供的排序規(guī)則。
這個(gè)排序算法會(huì)通過閉包中的準(zhǔn)則重復(fù)的兩兩比較列表中的Checklist對(duì)象,直到數(shù)組被排序完畢。
如果你想以其他標(biāo)準(zhǔn)來(lái)進(jìn)行排序,那么你就要修改閉包中的代碼。
實(shí)際的排序準(zhǔn)則是這個(gè):
checklist1.name.localizedStandardCompare(checklist2.name) ==
.orderedAscending
比較兩個(gè)Checklist對(duì)象,你只需要知道它們的名字。
localizedStandardCompare() 方法會(huì)結(jié)合區(qū)域中的規(guī)則比較兩個(gè)字符串,比較時(shí)會(huì)忽略大小寫(它會(huì)認(rèn)為A和a是一樣的)。
區(qū)域是一個(gè)對(duì)象,它知道一個(gè)國(guó)家中的語(yǔ)法細(xì)節(jié),比如Sorting在德語(yǔ)中的意思可能和英語(yǔ)中不太一樣。
這就是對(duì)一個(gè)數(shù)組進(jìn)行排序的全部工作:調(diào)用sort()并且給它一個(gè)閉包,閉包中包含著比較兩個(gè)Checklist對(duì)象的邏輯。
為了確保以存在的條目也被同樣的規(guī)則排序,你需要在plist文件被加載時(shí)也調(diào)用sortChecklists()方法:
func loadChecklists() {
...
sortChecklists()
}
}
運(yùn)行app并且新增幾個(gè)待辦事項(xiàng)分類。改變它們的名稱,觀察下是否已經(jīng)按照名稱排序了。
給待辦分類添加圖標(biāo)
因?yàn)檎嬲膇OS開發(fā)者不能無(wú)限制的添加視圖控制器和委托,所以我們要通過給Checklist對(duì)象添加一個(gè)新屬性的方法來(lái)增加圖標(biāo)。我們要把這些概念固化在你的腦袋里。
當(dāng)你完成后,編輯和新增待辦事項(xiàng)分類的界面會(huì)看起來(lái)是這個(gè)樣子:
當(dāng)你新增或者編輯待辦事項(xiàng)分類的時(shí)候,可以通過當(dāng)前界面打開一個(gè)新的界面來(lái)選擇圖標(biāo)。這個(gè)圖標(biāo)選擇器是一個(gè)新的視圖控制器。這一次你不會(huì)使用modally轉(zhuǎn)場(chǎng),而是會(huì)將它推入導(dǎo)航器的棧堆。
可以在課程附帶的Resources文件里找到相關(guān)的圖片文件,文件夾的名字是Checklist Icons(小伙伴們可以通過購(gòu)買正版圖書得到這個(gè)附件,也可以去網(wǎng)上下載一些自己喜歡的素材圖標(biāo)來(lái)練習(xí))
把圖標(biāo)從這個(gè)文件夾添加到Asset Catalog里。在工程導(dǎo)航起上選擇Assets.xcassets,點(diǎn)擊底部的加號(hào)按鈕,并且選擇Import
在文件選擇時(shí)打開Checklist Icons目錄,全選其中的文件。
注意:一定要選擇文件,而不是目錄。
然后點(diǎn)擊Open,導(dǎo)入就完成了。
每一個(gè)圖標(biāo)都有對(duì)應(yīng)Retina設(shè)備的2x版本和對(duì)應(yīng)Retina HD的3x版本。
就像我在之前的課程中說(shuō)過的一樣,你不需要1x的低分辨率圖標(biāo),所有的iPhone,ipad或者iPod,只要能運(yùn)行iOS 10,就一定是2x或者3x分辨率。
打開Checklist.swift,添加一條屬性:
var iconName: String
變量iconName存儲(chǔ)圖標(biāo)的文件名。
擴(kuò)展一下init?(coder)和encode(with),讓它們可以分別在Checklist.plist文件中讀取和存儲(chǔ)這個(gè)圖標(biāo)名稱。
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "Name") as! String
items = aDecoder.decodeObject(forKey: "Items") as! [ChecklistItem]
iconName = aDecoder.decodeObject(forKey: "IconName") as! String
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "Name")
aCoder.encode(items, forKey: "Items")
aCoder.encode(iconName, forKey: "IconName")
}
假如你以后想要自己給這個(gè)app擴(kuò)展什么功能的話,記住這一點(diǎn),你要把所有涉及到的新的屬性都添加到Checklist中,否則app就不會(huì)從plist文件中讀取或者存儲(chǔ)它們。
Xcode現(xiàn)在仍然有所不滿,它在抱怨init(name)方法中沒有這個(gè)新的屬性,報(bào)錯(cuò)信息為:Property self.iconName is not initialized at super.init call
這個(gè)意思是,如果Checklist對(duì)象用init(name)初始化,而不是用init?(coder)初始化時(shí),iconName沒有值。
更新一下init(name)方法:
init(name: String) {
self.name = name
iconName = "Appointments"
super.init()
}
這樣會(huì)給所有新的checklist一個(gè)“ Appointments”圖標(biāo)。
我猜你現(xiàn)在一定很想知道如何在table view上展示這個(gè)圖標(biāo)。不過在這之后,你還有更多的問題要操心,比如如何使用戶選擇圖標(biāo)。
打開AllListsViewController.swift,把圖標(biāo)放入cell中:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.imageView!.image = UIImage(named: checklist.iconName)
return cell
}
使用標(biāo)準(zhǔn)cell的.subtitle風(fēng)格時(shí),會(huì)自帶一個(gè)內(nèi)建的UIImageView在左邊。你可以簡(jiǎn)單的將圖片給它,然后它會(huì)自動(dòng)將圖片展現(xiàn)出來(lái),非常簡(jiǎn)單。
在重新運(yùn)行app前,刪除掉Checklist.plist文件,然后重置模擬器,因?yàn)槲覀兊奈募Y(jié)構(gòu)又發(fā)生了變化,如果不這樣做,app就隨時(shí)會(huì)死給你看。
運(yùn)行app,現(xiàn)在每個(gè)待辦分類都有了一個(gè)鬧鐘樣子的圖標(biāo):
改造非常成功,你現(xiàn)在可以改變一下Checklist的init(name)方法,給每個(gè)Checklist對(duì)象一個(gè)“No Icon”圖標(biāo)作為默認(rèn)。
打開Checklist.swift,在init(name)內(nèi),稍微改動(dòng)一下:
iconName = "No Icon"
"No Icon"是個(gè)完全透明的PNG圖片,它和其他圖片的大小一模一樣。使用透明圖片的必要性在于,這樣可以使所有的行格式一樣,即使用戶有時(shí)不想要一個(gè)圖片。
如果我們僅僅是把iconName設(shè)置為空的話,那么圖片位置就不會(huì)顯示出來(lái),標(biāo)簽會(huì)位于最左邊,看起來(lái)非常難看,大家可以比較一下沒有圖片和透明圖片的區(qū)別:
讓我們來(lái)創(chuàng)建圖片選擇界面:
添加一個(gè)Swift文件到工程中,取名為IconPickerViewController。
將該文件中的預(yù)置內(nèi)容全部刪掉,替換為以下代碼:
protocol IconPickerViewControllerDelegate: class {
func iconPicker(_ picker: IconPickerViewController,didPick iconName: String)
}
class IconPickerViewController: UITableViewController {
weak var delegate: IconPickerViewControllerDelegate?
}
這里定義了IconPickerViewController對(duì)象,它是一個(gè)table view controller,并且有有一個(gè)用來(lái)和這個(gè)app中其他對(duì)象通信的協(xié)議。
在class內(nèi)部,新增一個(gè)常量數(shù)組來(lái)保存各種圖片的名稱:
let icons = ["No Icon",
"Appointments",
"Birthdays",
"Chores",
"Drinks",
"Folder",
"Groceries",
"Inbox",
"Photos",
"Trips"]
這是一個(gè)包含圖片名稱的常量數(shù)組。這些字符串同時(shí)也是asset catalog中PNG文件的名稱。
這個(gè)數(shù)組就是這個(gè)視圖控制器的數(shù)據(jù)模型。注意一下,這個(gè)數(shù)組不會(huì)發(fā)生改變,因?yàn)樗怯胠et而不是var定義的,因?yàn)橛脩舨荒茉黾踊蛘邉h除圖片。
這個(gè)新的視圖控制器是一個(gè)UITableViewController,所以你需要執(zhí)行table view的數(shù)據(jù)源方法。
在文件中添加以下方法:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return icons.count
}
這里簡(jiǎn)單的返回了數(shù)組中對(duì)象的數(shù)量
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "IconCell", for: indexPath)
let iconName = icons[indexPath.row]
cell.textLabel!.text = iconName
cell.imageView!.image = UIImage(named: iconName)
return cell
}
這里你得到了一個(gè)table view cell并且給它了一個(gè)文本和圖片。稍候你會(huì)在故事模版里設(shè)計(jì)這個(gè)cell。
這是一個(gè)標(biāo)準(zhǔn)cell,風(fēng)格為“Default”(在界面建造器中對(duì)應(yīng)的名字是“Basic”)。這個(gè)風(fēng)格的cell自帶一個(gè)文本標(biāo)簽,一個(gè)圖片視圖,非常的便利。
打開故事模版。拖拽一個(gè)Table View Controller到畫布上,把它放在List Detail View Controller的旁邊(就是名字叫做“Add Checklist”)的那個(gè)。
打開這個(gè)新視圖的身份檢查器,將在class中填入IconPickerViewController。
選定cell,在屬性檢查器中設(shè)置Style(風(fēng)格)為Basic,Identifier為IconCell。
這樣,圖片選擇器的cell就設(shè)置好了。現(xiàn)在你需要在某些地方調(diào)用它。為了實(shí)現(xiàn)這個(gè)目的,你需要在Add/Edit Checklist界面添加一個(gè)新的行。
選擇List Detail View Controller,并且在table view中添加一個(gè)新的分節(jié)。你可以在table view的屬性檢查器中將Sections設(shè)置為2。這樣就有兩個(gè)分節(jié)了。
刪除掉新cell上的Text Field,你并不需要它。
新增一個(gè)標(biāo)簽到這個(gè)cell上,并且命名為Icon。
設(shè)置這個(gè)cell的Accessory為Disclosure Indicator,這樣你會(huì)得到一個(gè)灰色的大于號(hào)的按鈕。
添加一個(gè)Image View到這個(gè)cell的右邊,大小為36*36(可以使用尺寸檢查器來(lái)設(shè)置)
??:如果你的Mac OS版本為Sierra,Xcode中的Image View也許會(huì)展現(xiàn)為一個(gè)白色的矩形,非常難以觀察。我認(rèn)為這是Xcode的一個(gè)bug,在EI Capitan版本的Mac OS上,Image View就展現(xiàn)為一個(gè)藍(lán)色的矩形。
打開輔助編輯器為這個(gè)image view添加一個(gè)outlet,命名為iconImageView。(輔助編輯器中的文件應(yīng)該是在ListDetailViewController.swift)
這時(shí),兩個(gè)界面的設(shè)計(jì)都完成了,你現(xiàn)在可以使用轉(zhuǎn)場(chǎng)把它們連接起來(lái)了。
按住ctrl拖拽這個(gè)新的table view cell到Icon Picker View Controller并且將轉(zhuǎn)場(chǎng)的類型設(shè)置為Show。(確保你拖拽的是table view cell,而不是其中的圖片或者標(biāo)簽)
將這個(gè)新的轉(zhuǎn)場(chǎng)的identifier設(shè)置為PickIcon。
托轉(zhuǎn)場(chǎng)的福,這個(gè)新的視圖控制器有了一個(gè)導(dǎo)航欄。雙擊導(dǎo)航欄,將其標(biāo)題命名為Choose Icon。
??:如果雙擊無(wú)法修改導(dǎo)航欄標(biāo)題,你可以先拖拽一個(gè)Navigation Item到這個(gè)新的視圖控制器上。然后就可以修改標(biāo)題了,Xcode也是存在一些bug的。
現(xiàn)在你的故事模版中的這一部分,看起來(lái)應(yīng)該是這個(gè)樣子:
打開ListDetailViewController.swift,將table view的willSelectRowAt委托方法修改一下:
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if indexPath.section == 1 {
return indexPath
} else {
return nil
}
}
如果不這樣做的話,點(diǎn)擊“Icon”cell,就不會(huì)觸發(fā)轉(zhuǎn)場(chǎng)。
之前這個(gè)方法總是返回nil,就是說(shuō)不可能對(duì)這一行進(jìn)行點(diǎn)擊。現(xiàn)在你必須允許用戶點(diǎn)擊“Icon”cell所在對(duì)行,所以當(dāng)點(diǎn)擊這一行時(shí),需要返回它的indexPath。
因?yàn)椤癐con”cell是第二個(gè)分節(jié)中的一行,所以你要查看一下indexPath.section,這里并不需要檢查行號(hào),因?yàn)榈诙€(gè)分節(jié)只有一行。用戶仍然不能選擇第一個(gè)分節(jié)中的行。
運(yùn)行app,確認(rèn)一下,在添加和編輯Checklist的界面有一行是用于選擇圖標(biāo)的,并且點(diǎn)擊這一行后會(huì)展示圖標(biāo)的列表。
你可以點(diǎn)擊back按鈕來(lái)回到上一步,但是選擇圖標(biāo)功能還不會(huì)生效,你點(diǎn)擊一個(gè)圖標(biāo),僅僅是可以看到這一行變灰了,表示已經(jīng)被選中。
為了完成剩下的部分,你需要使用icon picker的委托協(xié)議把它和Checklist連接起來(lái)。
首先,在ListDetailViewController.swift中添加一個(gè)實(shí)例變量:
var iconName = "Folder"
你使用這個(gè)變量來(lái)跟蹤被選擇的圖標(biāo)名稱。
因?yàn)榧词笴hecklist對(duì)象已經(jīng)有了一個(gè)iconName屬性,你也不能保證百分百的追蹤到被選擇圖標(biāo)的名稱,原因很簡(jiǎn)單,因?yàn)橛锌赡艽藭r(shí)Checklist對(duì)象根本不存在,比如正在新建一個(gè)Checklist的時(shí)候。
所以你要把圖標(biāo)的名稱存儲(chǔ)到一個(gè)臨時(shí)變量里,并且在合適的時(shí)候把這個(gè)臨時(shí)變量的值拷貝到Checklist的iconName屬性中。
你要在合理的對(duì)iconName變量進(jìn)行初始化。僅對(duì)新建Checklist對(duì)象時(shí)需要這樣做,我們需要在新建Checklist對(duì)象時(shí),給它一個(gè)默認(rèn)圖標(biāo),就用文件夾圖標(biāo)好了。
改動(dòng)一下viewDidLoad()方法:
override func viewDidLoad() {
super.viewDidLoad()
if let checklist = checklistToEdit {
title = "Edit Checklist"
textField.text = checklist.name
doneBarButton.isEnabled = true
iconName = checklist.iconName //添加這一行
}
iconImageView.image = UIImage(named: iconName) //添加這一行
}
這里新增了兩行,如果checklistToEdit不為nil,那么你將Checklist對(duì)象的iconName拷貝到新建的iconName變量中,你同時(shí)還讀取圖標(biāo)的圖片文件到一個(gè)新的UIImage對(duì)象中,并且將它設(shè)置到iconImageView中,這樣圖標(biāo)就可以展示出來(lái)了。
之前你創(chuàng)建的轉(zhuǎn)場(chǎng)名稱叫做“PickIcon”,你還需要執(zhí)行prepare(for:sender:)來(lái)告訴IconPickerViewController這個(gè)界面是它的委托。
打開ListDetailViewController.swift,添加下面的方法:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PickIcon" {
let controller = segue.destination as! IconPickerViewController
controller.delegate = self
}
}
這對(duì)你應(yīng)該很輕車熟路了。
當(dāng)然,Xcode已經(jīng)發(fā)現(xiàn)了有些事情不對(duì),并且給出了一個(gè)大大的警告:在“controller.delegate = self”這一行。
Xcode的意思是:“Cannot assign a value of type 'ListDetailViewController' to a value of type
'IconPickerViewControllerDelegate?'”(不能把類型ListDetailViewController的值分配給IconPickerViewControllerDelegate)
練習(xí):我們忘記了什么?
答案:你還沒有讓這個(gè)視圖控制器遵循委托協(xié)議,所以Swift不會(huì)使ListDetailViewController成為icon picker的委托。
在class這一行添加一點(diǎn)東西進(jìn)去:
class ListDetailViewController: UITableViewController,UITextFieldDelegate,IconPickerViewControllerDelegate
并且執(zhí)行委托協(xié)議中的方法,隨便放在什么地方都可以,但是要在ListDetailViewController class的里面:
func iconPicker(_ picker: IconPickerViewController, didPick iconName: String) {
self.iconName = iconName
iconImageView.image = UIImage(named: iconName)
let _ = navigationController?.popViewController(animated: true)
}
這里將被選擇的圖標(biāo)名稱存放到iconName變量中,并且用新的圖片更新了image view。
這里不能調(diào)用dismiss()而是調(diào)用popViewController(animated),因?yàn)镮con Picker位于導(dǎo)航棧堆中。當(dāng)創(chuàng)建這個(gè)轉(zhuǎn)場(chǎng)的時(shí)候你使用了Show而不是present modally,所以這里要使用“pop”,dismiss()僅用于present modally。
回憶一下,navigationController是這個(gè)視圖控制器的一個(gè)可選型的屬性,所以你使用問號(hào)或者感嘆號(hào)來(lái)解包得到實(shí)際的UINavigationController對(duì)象。通過let _ =你告訴了Xcode不需要關(guān)心popViewController()的返回結(jié)果。不這樣做的話,Xcode又會(huì)給出一個(gè)警告。這里的下劃線符號(hào)叫做通配符,你可以用具體的變量名稱來(lái)替換它。
??:你已經(jīng)知道了self用于引用對(duì)象自己。這里你寫的:
self.iconName = iconName
理由是這里iconName可以指代兩個(gè)不同的東西:1、委托方法的參數(shù)。2、實(shí)例變量。
為了消除歧義,你用帶有self前綴的來(lái)表示實(shí)例變量,這樣編譯器就很清楚你想要用的是這兩個(gè)iconName中的那一個(gè)。
改變一下done()方法,以便于將被選擇的icon name放入Checklist對(duì)象。
@IBAction func done() {
if let checklist = checklistToEdit {
checklist.name = textField.text!
checklist.iconName = iconName //添加這一行
delegate?.listDetailViewController(self, didFinishEditing: checklist)
} else {
let checklist = Checklist(name: textField.text!)
checklist.iconName = iconName //添加這一行
delegate?.listDetailViewController(self, didFinishAdding: checklist)
}
}
最后,你必須改變IconPickerViewController,使它在某一行被點(diǎn)擊時(shí),實(shí)際的調(diào)用委托方法。
打開IconPickerViewController.swift,在底部添加一個(gè)方法:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let delegate = delegate {
let iconName = icons[indexPath.row]
delegate.iconPicker(self, didPick: iconName)
}
}
現(xiàn)在你就完成了對(duì)Checklist對(duì)象圖標(biāo)設(shè)置的工作。
回顧一下,你做了以下事情:
1、添加一個(gè)新的視圖控制器
2、在故事模版中設(shè)計(jì)界面
3、使用轉(zhuǎn)場(chǎng)和委托將這個(gè)視圖控制和添加及編輯Checklist界面連接起來(lái)。
當(dāng)你每新建一個(gè)視圖控制的時(shí)候,都需要完成以上基本步驟。
運(yùn)行app,試試效果如何: