18.%d Xcode Study — Passing Data using Delegate/Closure
目錄
⦿ 傳值回顧
⦿ Delegate 傳值基礎設置
⦿ Delegate、protocol與反向傳值
⦿ 再看 Delegate
⦿ closure 與反向傳值
傳值回顧
在前一篇文章中,我們使用了更不一樣的做法來傳值,KVC 與 KVO,KVC 是利用繼承 NSObject 的 class 生成的物件,保有 NSKeyValueCoding
的特性去傳值。
在 KVC 中,其實我們可以把它看成 property 傳值的延伸,不過既然是 KVC,就有特殊取值與設定的方式;KVO 則是在 KVC 物件基礎,加上 Listener,所以當物件的值改變時,會跟著改變一些其他東西,無論是 property,或是隨 property 變動的 UI。
這篇文章中,我們再來看看其他傳值方式,Delegate 與 Closure。
繼續閱讀|回目錄
Delegate 傳值基礎設置
在 iOS 開發中,我們常使用 TableView,所以也可以說常使用 UITableViewDataSource,DataSource 就是 Delegation Pattern
(委任模式)的實際應用。
當一個 VC conform UITableViewDataSource
,就可以使用下方的程式碼:
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default,
reuseIdentifier: "Cell")
let country = countries[indexPath.row]
cell.textLabel?.text = "\(country.emoji) \(innerBlank)\(country.dial_code)"
return cell
}
第一個 function 表示,顯示的 Table 有多少 Row,藉由回傳資料有多少個 Set 來作為 Row 的數量(Int)。
第二個 function 表示,Table 要顯示的 Cell 的 ID 是 Cell
,每個 Cell 透過預設的 style 去設定它的標題
,最後回傳這個 Cell。
當 VC conform 這個 DataSource,就可藉由這基本兩個 function 設定欲顯示的 Table,於是程式碼就會少很多。軟體工程師設計程式碼的其中一個原則就是減少重複
,而這邊要特別注意的是 DataSource 是一個 protocol。
因為 conform 這個 protocol,所以 VC 可以代替 Table 做一些事,在這裡是設定它的顯示資料。而 Delegation Pattern 在 iOS 關乎另一個重要的功能—— Passing Data(傳值)。
我們先看到下面的基本設置:
左邊 Gif 圖是呈現的結果,在第一頁按下選擇鍵
,會跳出第二頁讓使用者選擇性別
,女 / 男 / 無,每個性別會跟著一張 Avatar,若不選擇回到第一頁,第一頁什麼都不會變。
選擇每張圖都會跟著不同的背景色
(黃 / 橘 / 綠)帶回第一頁,當然,性別欄跟 ImageView 也會帶回這三張不同的 image
。
以上是頁面安排的基本,接著看到 ViewController(第一頁)的程式碼:
@IBAction func choosingBtnTapped(_ sender: Any) {
let vc = storyboard?.instantiateViewController(withIdentifier: "\(MyTableViewController.self)") as! MyTableViewController vc.delegate = self
present(vc, animated: true, completion: nil)
}
如往常般,從 Storyboard 中產生 VC,接著 show 出 VC,暫時先忽略 vc.delegate = self
,這是 Delegation Pattern 的核心。
再看到 MyTableViewController(第二頁)的程式碼:
@IBAction func firstBtnTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func secondBtnTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func thirdBtnTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
在這邊有一個小 trick
,我們可以對照前段的 Storyboard 設置,ImageView 是方便擺設圖片的 UIElement,若希望點擊圖片作動(action),可以使用動作偵測(Tap Gesture),但也可以在 ImageView(image)上方放上相同大小的 Button,接著再將 Button 的顏色設置為 .clear
,這時就可以穿透 Button 看到 image,我們點擊 image 也等於點擊 Button。
所以這些 IBAction 就是從 Button 拉出來的,而在過去的文章中,我們學過 Outlet Collection
,同樣的方法也可以應用在 Action 中,我們若將所有的 Button Action 放在一起就會形成如下:
@IBAction func btnTapped(_ sender: UIButton) {
// MARK: - delegation pattern
let userData = userDatas[sender.tag]
delegate.didChoosing(image: userData.avatar,
gender: userData.gender,
color: userData.customColor)
dismiss(animated: true, completion: nil)
}
同樣地,我們只先注意 dismiss,在按下 Button 就會解散回到上一頁;但也要注意 Storyboard 中,Button 的 tag
,這個 tag 也如同 Outlet Collection 中,我們用來區分不同 Outlet 的編號
。
繼續閱讀|回目錄
Delegate、protocol 與反向傳值
在 Delegation Pattern 中的一個特性是,當這個頁面
還沒產生時,仍可以透過一個接口 / 介面與它
產生連結。
在 iOS 開發中所謂協定導向
就是用來作為接口 / 介面的方式,我們先設置一個 protocol(接口)如下:
protocol AvatarChoosingDelegate {
func didChoosing(image: UIImage, gender: String, color: UIColor)
}
這個接口
叫做頭像選擇的代理
,要實作一個名稱叫已選擇
的 function,裡面要傳遞的 property 為 image、gender、color,型別分別為 UIImage、String、UIColor,這些 property 就是要傳回第一頁,並設定第一頁的 UIElement 的資訊。
還記得前面說到第二頁尚未產生,這即是說,第一頁要代理第二頁完成這個設定(實作 function),我們只要著重在接口兩邊的資料傳遞,在第二頁先宣告一個 property 叫做 delegate 如下:
var delegate: AvatarChoosingDelegate!
下面則是接口傳遞資料:
在選擇 Avatar 後,透過呼叫 delegate 的 function 將資料放進去,接著回到上一頁。
而前面說過,這個設定的 function 還未實作,所以回到 ViewController 中,加上下面這個 extension:
extension ViewController: AvatarChoosingDelegate { func didChoosing(image: UIImage, gender: String, color: UIColor) { avatarImageView.image = image
genderLabel.text = gender
view.backgroundColor = color }}
由於接口將資料傳遞過來,所以在這邊就可以使用資料設定 UI。
但是別忘了!我們回到前面,在第二頁剛剛產生,我們需要將第二頁的 delegate 設定為 self。
如此!透過 Delegate 傳值就完成了!
繼續閱讀|回目錄
再看 Delegate
經過前面,我們知道 Delegate 是基於 Protocol 下的一種使用方法,而 UITableViewDataSource 也是如此,當 tableView 的 dataSource 是 VC,就可以將程式碼的權責
分開,當 VC conform 此 Protocol,就可對 tableView 提供 dataSource。
前面的 tableView
numberOfRowsInSection 與 tableView
cellForRowAt 即是如此,將資料有多少個 Set 提供給前者,再將各個 cell 欲呈現什麼資料項提供給後者,也是 tableView 的基礎
。
在 cellForRowAt,我們最常看到一段程式碼為 let cell = tableView.dequeueReusableCell(withIdentifier: “myCellIdentifier”, for: indexPath)
,這是為了在產生 cell 時不造成記憶體的浪費,使用 dequeue
讓產生過現在不出現在畫面上的 cell 是可以重用
的,當然,此時點擊或 loading 的顯示內容就成了 issue,不過這邊不提。
在傳值中,我們可以這麼理解,如果我
(第二頁)要傳值給先於我產生的 View
(第一頁),在我
還沒產生的時候,這個 View
要替我做什麼?例如將從我
這邊來的 dataSource 用以設定 View
本身。
所以 View
只要 conform protocol 去實作 function;而我
則是宣告 delegate 為這個 protocol(protocol 可以做為型別
來使用),當我
要傳遞資料時就會觸發傳遞資料的方法,由於 View
代替我
做了這件事,所以回到上一頁時,View
的 Appearance 也跟著改變了。
這就是 Delegation Pattern 傳值的方式。
繼續閱讀|回目錄
closure 與反向傳值
要使用閉包傳值,我們要先知道閉包如何宣告,閉包就是匿名函式,也是一個執行區塊(Block)。
所以得先知道閉包要傳遞什麼,在第二頁的地方,我們會像這樣宣告:
var choosingCompletion: ( (UserData) -> Void )?
這個 UserData 是我們要傳過去的資料,也就是閉包的參數,閉包的回傳值是 Void,並且這個閉包必須是 Optional 的。
而當選擇鍵按下後,程式碼如下:
@IBAction func btnTapped(_ sender: UIButton) { choosingCompletion?(userDatas[sender.tag])
dismiss(animated: true, completion: nil)
}
必須代入參數。
在第一頁的地方,程式碼如下:
@IBAction func chooseBtnTapped(_ sender: Any) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "\(MyTableViewController.self)") as? MyTableViewController else { return } vc.choosingCompletion = { [weak self] userData in
self?.avatarImageView.image = userData.avatar
self?.genderLabel.text = userData.gender
self?.view.backgroundColor = userData.customColor
} present(controller, animated: true, completion: nil)
}
實際上,你必須了解,雖然 vc.choosingCompletion
寫在這裡,但它卻是在第二頁選擇後才觸發。
最終結果如下:
這次就分享到這,感謝您的閱讀。
繼續閱讀|回目錄
附上 GitHub: