在 instantiateViewController 傳資料 — iOS 13 新功能
除了透過 segue 實現 iOS App 的頁面切換,我們也可以拋棄 segue,直接用 UIStoryboard 的 function instantiateViewController 從程式建立 storyboard 設計的 controller。然而若想將資料傳給下一頁的 controller,其實有點麻煩,比方以下點選某首歌的 cell 後切換到下一頁顯示歌詞的例子。
傳資料的程式如下,我們得先將 instantiateViewController 生成的 controller 轉型,然後才能將資料存入它的 property。
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let song = songs[indexPath.row]
if let detailController = storyboard?.instantiateViewController(identifier: "SongDetail") as? SongDetailViewController {
detailController.song = song
show(detailController, sender: nil)
}
}
從 iOS 13 開始,我們可以在 instantiateViewController 建立 controller 時指定採用的 init,直接傳資料給 controller,步驟如下:
從 storyboard 設計畫面
列表頁是 SongTableViewController,明細頁是 SongDetailViewController,它們之間沒有 segue 相連。
在 SongDetailViewController 定義 init,設定前一頁傳來的資料
class SongDetailViewController: UIViewController {
let song: Song
init?(coder: NSCoder, song: Song) {
self.song = song
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError()
}
當我們透過 instantiateViewController 建立下一頁的 controller 時,我們可以指定採用的 init,因此傳資料的關鍵就在我們自訂下一頁 controller 的 init,在 init 裡包含我們想傳遞的資料當參數。
假設我們希望從列表頁傳到 detail 頁的資料型別是 Song,因此我們定義 init?(coder: NSCoder, song: Song)
,參數 song 將接收前一頁傳來的資料。值得注意的,我們必須同時包含型別 NSCoder 的參數,並在 init 裡呼叫 super.init(coder: coder)
,因為我們的 controller 是在 storyboard 設計,它的初始必須經過 init(coder:)。
ps: required init?(coder: NSCoder) 也要定義,詳細原因可參考以下連結。
利用 instantiateViewController(identifier:creator:) 建立 controller 和傳資料
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let song = songs[indexPath.row]
let detailController = storyboard?.instantiateViewController(identifier: "SongDetail", creator: { coder in
SongDetailViewController(coder: coder, song: song)
})
if let detailController {
show(detailController, sender: nil)
}
}
iOS 13 的 instantiateViewController(identifier:creator:) 讓我們在建立 controller 時可以在參數 creator 傳入 closure,在 closure 裡產生要建立的 controller。
func instantiateViewController<ViewController>(identifier: String, creator: ((NSCoder) -> ViewController?)? = nil) -> ViewController where ViewController : UIViewController
因此我們用 SongDetailViewController(coder: coder, song: song)
建立 controller ,在建立 controller 時傳入 song,實現將資料傳給下一頁 controller 的功能,這也說明了為何我們之前要在 SongDetailViewController 裡定義 init。