在 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。

完整範例連結

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com