利用 IBSegueAction 傳資料到下個頁面的 controller — iOS 13 新功能

當我們在頁面之間切換時,時常需要將資料傳到下一頁的 controller,比方下圖的分手情歌列表,我們點選某首歌的 cell 後切換到下一頁顯示它的經典歌詞。

從 iOS 13 開始,類似這樣的應用我們可以搭配 IBSegueAction 實現。當 segue 綁定 IBSegueAction 定義的 function 時,App 在透過 segue 切換頁面時會呼叫 IBSegueAction 定義的 function,我們可在 function 裡建立下個頁面的 controller 和傳資料給 controller。接下來就讓我們以剛剛的分手情歌 App 為例一步步說明吧。

從 storyboard 設計畫面

列表頁是 SongTableViewController,明細頁是 SongDetailViewController。

在 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()   }

當我們透過綁定 IBSegueAction 的 segue 切換頁面時,我們可以自己從程式建立下一頁的 controller,因此傳資料的關鍵就在我們自訂下一頁 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) 也要定義,詳細原因可參考以下連結。

在列表頁 SongTableViewController 定義 @IBSegueAction function

當我們的 segue 是從 controller A 到 controller B,而我們想透過 IBSegueAction 傳資料時,必須在 controller A 裡定義 @IBSegueAction 開頭的 function。

此 function 我們不用自己寫,可以像拉 @IBAction 一樣,用拉線的方式讓 Xcode 幫我們產生。如下圖所示,我們從 segue 拉線產生 IBSegueAction function,輸入它的名字 showDetail。

如下圖所示,產生的 function 將以 @IBSegueAction 開頭,回傳下一頁的 controller 型別。

接著我們修改 showDetail,將資料傳給下一頁的 SongDetailViewController。

@IBSegueAction func showDetail(coder: NSCoder) -> SongDetailViewController? {   if let row = tableView.indexPathForSelectedRow?.row {      let song = songs[row]      return SongDetailViewController(coder: coder, song: song)   } else {      return nil   }}

傳資料的關鍵在以下這行程式。我們在建立下一頁的 SongDetailViewController 時傳入 song,這也說明了為何我們之前要在 SongDetailViewController 裡定義 init。

return SongDetailViewController(coder: coder, song: song)

此時我們也可從 segue 的 Attributes inspector 看到它果然確確實實連到了 function showDetail:。

完整範例連結

另一種做法

如果不想在 SongDetailViewController 定義 init,我們也可以先用預設的 init(coder:) 產生 controller,然後再將資料存入它的 property。不過這時 property song 的宣告要改成 var song: Song!,因為它一開始沒有內容,要等我們設定後才有值。

@IBSegueAction func showDetail(_ coder: NSCoder) -> SongDetailViewController? {   if let row = tableView.indexPathForSelectedRow?.row {      let controller = SongDetailViewController(coder: coder)      controller?.song = songs[row]      return controller   } else {      return nil   }}

有多個 segue 時,可用 sender & segue id 區分

當有多個 segue 連到同一個 @IBSegueAction function 時,我們可用 sender & segue id 區分。

剛剛在拉線產生 IBSegueAction 時,我們可調整 Arguments,改變它的參數,因此選擇最複雜的 Sender & Identifier 時,將多出參數 sender & segueIdentifier。

sender 是觸發 segue 的元件,segueIdentifier 是 segue 的 id。

利用 IBSegueAction 傳資料的步驟說明

假設 controller A 從 segue 切換到 controller B。

  • controller B 宣告儲存資料的 property。
  • controller B 裡定義 init,參數包含前一頁要傳入的資料,在 init 裡利用參數設定 property。
  • 從 segue 連線,在 controller A 裡產生 IBSegueAction function,在 function 裡建立 controller B 時傳入要給它的資料。

和舊做法 prepare 的比較

當我們用 segue 切換頁面時,也可以透過 function prepare 傳資料到下個 controller。

不過相較之下它卻有個缺點。以剛剛的分手情歌為例,detail 頁面 controller 的 song 應該一定有值,而且不會改變,所以我們希望能將它定義成 let song: Song 。然而若以 function prepare 傳資料,不透過 IBSegueAction,下一頁的 controller 將由系統自動生成,我們無法在它生成時設定 song 的內容,因此只能將它宣告成 optional 的變數,var song: Song?。

--

--

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

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