iOS Apprentice中文版-從0開始學(xué)iOS開發(fā)-第二十五課

來(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è)樣子:

你可以為分類選擇一個(gè)圖標(biāo)

當(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)

把圖標(biāo)從這個(gè)文件夾添加到Asset Catalog里。在工程導(dǎo)航起上選擇Assets.xcassets,點(diǎn)擊底部的加號(hào)按鈕,并且選擇Import

加號(hào)按鈕在這里

在文件選擇時(shí)打開Checklist Icons目錄,全選其中的文件。

全選文件,而不是選擇目錄

注意:一定要選擇文件,而不是目錄。

然后點(diǎn)擊Open,導(dǎo)入就完成了。

導(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):

圖標(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)的列表。

像這樣就對(duì)了

你可以點(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,試試效果如何:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容