①②⑥ iOS 頁面間的資料傳遞-往前傳

Data Passing Between Controllers — Forward

Min
彼得潘的 Swift iOS / Flutter App 開發教室
16 min readFeb 8, 2024

--

每次在開始進行一個專案之前,都會想:『這次要用哪個方法傳資料呢?』

在不同的情境下,要使用不同傳資料的方式。過年要大掃除跟整理環境,我們就來把各種傳資料的方法整理一遍吧!

傳遞資料的方法大致分為三種類型:

  • 往前傳 (Passing data forward):傳到下一個頁面。
  • 往回傳 (Passing data back):往前回傳。
  • 雙向傳:不限制傳遞的方向,不過有些有比較推薦的方向,或可以但不建議使用的方法。

這篇整理五種往前傳的方式:

IBSegueAction
- 基礎版
- init 版
Segue (prepare(for:sender:))
show(_:sender:)
present(_:animated:completion:)
pushViewController(_:animated:)

使用 IBSegueAction 傳遞資料- 基礎版

範例使用兩個 Controller,使用者在 MainViewController 上選擇一個顏色之後,按下 button 會到下一頁 ShowColorViewController,看到剛剛選的顏色成為下一頁的背景色。

可能因為是 iOS 13 的新方法,我覺得使用起來非常方便。三個步驟就好,一二步驟跟三可對調先後順序。

步驟一:

將 MainViewController 的 Button 拉 Show Segue 到 ShowColorViewController。

步驟二:

在此 segue 身上的節點,拉線到 MainViewController,設定方法。

import UIKit

class MainViewController: UIViewController {
// 色彩選擇器的 IBOutlet
@IBOutlet weak var colorWell: UIColorWell!

override func viewDidLoad() {
super.viewDidLoad()

}
// 連接 SegueAction 的方法,回傳下一頁的 ViewController
@IBSegueAction func segueToDestination(_ coder: NSCoder) -> ShowColorViewController? {
// 宣告一個下一頁 ViewController 的實例
let showColorViewController = ShowColorViewController(coder: coder)
// 指派 colorWell 的顏色為將下一頁 Controller 中的 color 屬性
showColorViewController?.color = colorWell.selectedColor
// 回傳下一頁的 ViewController 實例
return showColorViewController
}
}

步驟三(也可先設定):

在 ShowColorViewController 裡設定 color 屬性,用來在 MainViewController 中將使用者選好的顏色傳過來,當作接收器的意思。然後在 viewDidLoad 設定 color 為背景顏色。

import UIKit

class ShowColorViewController: UIViewController {
// 設定 color 屬性用來接收上一頁傳過來的顏色
var color: UIColor!

override func viewDidLoad() {
super.viewDidLoad()
// 設定背景顏色為 color
view.backgroundColor = color
}
}

這樣就 OK 了。選擇顏色後,傳到下一頁變成背景顏色:

回頁首回分類表

使用 IBSegueAction 傳遞資料- init 版

IBSegueAction 方法已經很簡單了,但有更簡易的方法!

回到剛剛的第三步驟:

把 ShowColorViewController 的 color 屬性的 optional 去掉,變成一個常數。但這樣必須要初始化 class ShowColorViewController,因此寫一個 init 方法,除了初始化自己的屬性,也呼叫父類別的 init:

    // 初始化 class
init?(coder: NSCoder, color: UIColor) {
self.color = color
super.init(coder: coder)
}

還有必備的另一個 fatalError 的 requied init:

    required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

這兩個 init 沒寫,編譯器都會給提示。

原本的第二步驟因此能更簡化了。回到 MainViewController, 在 IBSegueAction 方法內只要一行,把剛剛初始化需要的東西傳過去即可!

    // 連接 SegueAction 的方法,回傳下一頁的 ViewController
@IBSegueAction func segueToDestination(_ coder: NSCoder) -> ShowColorViewController? {
ShowColorViewController(coder: coder, color: colorWell.selectedColor!)
}

因為有預設色彩選擇器的顏色,因此一定有顏色,不怕 app 崩潰:

回頁首回分類表

使用 Show Segue 與 prepare(for:sender:) 傳遞資料

這個方式只要拉完 show segue 之後,就不用再使用 storyboard 上的這條線了,因為 prepare 方法用的是 segue 身上的 identifier 來找到它。

像剛剛的 IBSegueAction 用在單純一個 button 傳到下一個頁面是很方便的,但如果是點選 Table View Cell 出發,使用 prepare 是常見的選擇。

這次出發 Controller 換成 TableViewController,使用 Dynamic Prorotype。點選海洋生物的名字後,到下一頁顯示它們的 emoji。

連結 Segue 到負責顯示 emoji 的 ShowSeaCreaturesViewController 之後,設定 Segue 的 Identifier 為 SeaCreatureIdentifier,我們將用這個 Identifier 來傳輸資料到下一頁。

設定好三種海洋生物跟他們的 emoji:

struct SeaCreatures {
let name: String
let emoji: String
}


var seaCreatures = [
SeaCreatures(name: "shark", emoji: "🦈"),
SeaCreatures(name: "blowfish", emoji: "🐡"),
SeaCreatures(name: "lobster", emoji: "🦞")
]

在 TableViewController,先設定表格內容。一個 section,row 的數量為 seaCreatures 的數量,Cell 文字來自 seaCreatures 的 name:

    override func numberOfSections(in tableView: UITableView) -> Int {
1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
seaCreatures.count
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)
let seaCreature = seaCreatures[indexPath.row]
cell.textLabel?.text = seaCreature.name

return cell
}

接著來準備傳遞資料, TableViewController 內有已經註解起來的 prepare 方法,解開來使用!

prepare 內主要要確定 segue 是哪一條,接著用 segue.destination設定目標 ViewController 的實例 destinationViewController,並轉換為 ShowSeaCreaturesViewController 的型別。接著把 emoji 傳過去:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 確認是前往 ShowSeaCreaturesViewController 的 segue
if segue.identifier == "SeaCreaturesIdentifier" {
// 取得目標 ViewController 的實例,並轉換為 ShowSeaCreaturesViewController 類型
if let destinationViewController = segue.destination as? ShowSeaCreaturesViewController {
// 取得點擊的 cell 的索引路徑
if let indexPath = tableView.indexPathForSelectedRow {
// 取得對應位置海洋生物的 emoji
let seaCreature = seaCreatures[indexPath.row].emoji
// 將資料傳遞給 ShowSeaCreaturesViewController
destinationViewController.selectedSeaCreature = seaCreature
}
}
}
}
回頁首回分類表

不使用 Segue 傳遞資料- show(_:sender:)

刪掉剛剛海洋生物在 Storyboard 上的 segue,在 ShowSeaCreaturesViewController,將 Storyboard ID 設定為 showSeaCreaturesViewController:

接著刪掉 prepare 方法,改成以下:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 取得對應位置海洋生物的 emoji
let seaCreature = seaCreatures[indexPath.row].emoji

// 從Storyboard 中取得 showSeaCreaturesViewController 的實例,並檢查這個實例是否可轉換為 ShowSeaCreaturesViewController 類型
if let viewController = storyboard?.instantiateViewController(withIdentifier: "showSeaCreaturesViewController") as? ShowSeaCreaturesViewController {
// 將 seaCreature 傳到 viewController
viewController.selectedSeaCreature = seaCreature
// show 顯示 viewController
show(viewController, sender: nil)
}
}
回頁回分類表

不使用 Segue 傳遞資料- present(_:animated:completion:)

大家設定都相同,只是 show 改成 present,completion 設定為 nil 即可:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 取得對應位置海洋生物的 emoji
let seaCreature = seaCreatures[indexPath.row].emoji
// 從Storyboard 中取得 showSeaCreaturesViewController 的實例,並檢查這個實例是否可轉換為 ShowSeaCreaturesViewController 類型
if let controller = storyboard?.instantiateViewController(withIdentifier: "showSeaCreaturesViewController") as? ShowSeaCreaturesViewController {
controller.selectedSeaCreature = seaCreature
// present 顯示 viewController
present(controller, animated: true, completion: nil)
}
}
回頁首回分類表

不使用 Segue 傳遞資料- pushViewController(_:animated:)

這個方式也是替換掉 show 或 present 的那行程式,不過需要在 Storyboard 先把 TableViewController 放到 Navigation Controller 中,我們需要借助它的力量:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// // 取得對應位置海洋生物的 emoji
let seaCreature = seaCreatures[indexPath.row].emoji
// 從Storyboard 中取得 showSeaCreaturesViewController 的實例,並檢查這個實例是否可轉換為 ShowSeaCreaturesViewController 類型
if let controller = storyboard?.instantiateViewController(withIdentifier: "showSeaCreaturesViewController") as? ShowSeaCreaturesViewController {
// 將 controller 推送到 navigationController 的 stack 中,將畫面導航到 controller
navigationController?.pushViewController(controller, animated: true)
// 設定海洋生物的 emoji
controller.selectedSeaCreature = seaCreature
}
}

因為有了 Navigation Controller,畫面改由左右切換:

回頁首回分類表

--

--