昨天,我們成功地讓 AI 化身為設計師,根據文字描述生成了專業的配色方案。這證明了透過精準的 Prompt,我們可以讓 AI 穩定地輸出結構化的資料。
今天,我們要挑戰打造一個「AI 夢境視覺化師」,讓使用者輸入一段夢境的文字描述,AI 就能為我們生成一張代表那個夢境的圖像。
困惑點:我的 App 會有兩個畫面:一個是讓使用者輸入夢境的「輸入頁」,另一個是顯示 AI 生成圖片的「結果頁」。當使用者在「輸入頁」點擊「生成」按鈕後,我要如何將那段文字傳遞到「結果頁」去?在 Day 8,我們學過用 Delegate 模式來「回傳」資料,但這次是「往前傳」,這兩種情境的最佳實踐有什麼不同嗎?
AI 協作
我正在開發一個 App,有兩個 ViewController:InputViewController 和 ResultViewController。當使用者在 InputViewController 的 UITextView 輸入文字並點擊按鈕後,我需要將這段文字傳遞到 ResultViewController 並顯示出來。請問,在 iOS 開發中,這種「由前一頁傳值到下一頁」最直接、最標準的作法是什麼?請簡單說明並不需要給程式碼
AI 的回答:
您好!「由前一頁傳值到下一頁」(Forward Data Passing)是 iOS 開發中最基本也最常見的操作之一。相較於用 Delegate 或 Closure 處理「由後一頁回傳值」,往前傳遞資料的方式要簡單直觀得多。
最標準且推薦的做法是「屬性注入 (Property Injection)」。
這個方法的核心思想是:在您準備要呈現下一個 ViewController 之前,直接設定它的一個公開屬性 (Public Property)。
操作流程比喻:
您可以把這個過程想像成「寄送一封信」:
ResultViewController
(收件人):它身上有一個公開的「信箱」(一個public
或internal
的String
屬性),專門用來接收信件。例如:var dreamDescription: String?
。InputViewController
(寄件人):當它準備要寄信時 (也就是使用者點擊按鈕,準備跳轉畫面的時候),它會做三件事:
- 建立信件 (資料):從
UITextView
中取得使用者輸入的文字。- 建立收件人地址 (實例化):建立
ResultViewController
的一個實例 (instance)。- 投遞信件 (賦值):在將畫面 push 或 present 出去之前,直接透過這個實例,將信件內容 (文字) 投遞到「收件人」的「信箱」(屬性) 裡。例如:
resultVC.dreamDescription = "我夢見了..."
。ResultViewController
(收件人):當它被呈現到畫面上時 (例如在viewDidLoad
生命週期中),它會檢查自己的「信箱」,如果裡面有信,就拿出來閱讀 (更新到自己的 UI 上)。為什麼這是最佳實踐?
- 簡單直觀:這是最直接的方式,程式碼的意圖非常清晰:「我要去下一頁,順便把這個東西帶過去」。
- 低耦合:
InputViewController
只需要知道下一頁有一個可以接收String
的屬性,但它完全不需要知道下一頁會如何使用這段文字。同樣地,ResultViewController
也不需要知道是誰把資料傳給它的,它只管處理好自己收到的資料。- 類型安全:因為是直接為強型別 (Strongly-typed) 的屬性賦值,所以 Swift 的編譯器會在編譯時期就幫您檢查型別是否正確,有效避免了執行期的錯誤。
總結來說,當您需要將資料從 A 頁面傳遞到 B 頁面時,最簡單且專業的方式就是在 B 頁面宣告一個公開的屬性,然後在 A 頁面建立 B 的實例後、跳轉之前,直接為這個屬性賦值。
InputViewController
的 UI,包含一個 UITextView
和一個 UIButton
。ResultViewController
,並在上面放置一個 UITextView
和一個 UIImageView
,同時定義好接收資料的屬性。InputViewController
中,實作按鈕點擊事件,完成「建立 ResultViewController
-> 屬性賦值 -> Push 畫面」的完整流程。我們建立一個新的 Xcode 專案,命名為 AIDreamVisualizer
。按照慣例,我們移除 Storyboard,並建立一個 InputViewController.swift
及其 xib 檔。
接著,我們打開 InputViewController.xib
來佈局 UI:
輸入框 (UITextView
):從元件庫 (+) 拖曳一個 UITextView
到畫布上。這是讓使用者輸入夢境的主要區域。
生成按鈕 (UIButton
):在 UITextView
下方,放置一個 UIButton
,標題設為「生成」。
狀態標籤 (UILabel
):在按鈕下方,放置一個 UILabel
,用來顯示目前的處理狀態。
約束:為所有元件設定好約束,確保它們在不同尺寸的螢幕上都能正確顯示。
連結 IBOutlet 與 IBAction:將 UITextView
、UILabel
和 UIButton
都連結到 InputViewController.swift
中。
為了讓 UITextView
的使用體驗更好,我還加上了點擊空白處或工具列按鈕來收起鍵盤的功能。
(可以透過 SPM 安裝 IQKeyboardManagerSwift 來自動化處理鍵盤。安裝完成後,最重要的一步是在 AppDelegate (或 App 啟動點) import IQKeyboardManagerSwift
並加入 IQKeyboardManager.shared.enable = true
來全域啟用它,之後就無需手動撰寫收合鍵盤等功能了。)可以在Day 9 - 數據儲存:認識 RealmSwift,為之後的專案做準備參考我的SPM安裝
不過,為了徹底理解 iOS 鍵盤的處理機制,我們在這個系列中,將會先用手動的方式來完成這個功能。
檔案:InputViewController.swift
import UIKit
class InputViewController: UIViewController, UITextViewDelegate {
// MARK: - IBOutlet
@IBOutlet weak var lbStatus: UILabel!
@IBOutlet weak var txfDream: UITextView!
@IBOutlet weak var btnSent: UIButton!
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
title = "輸入你的夢境"
setupKeyboardHandling()
setupTextView()
}
// MARK: - UI Setting
private func setupKeyboardHandling() {
// 添加點擊手勢來收起鍵盤
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
private func setupTextView() {
// 設置 TextView 的屬性
txfDream.delegate = self
txfDream.layer.borderColor = UIColor.systemGray4.cgColor
txfDream.layer.borderWidth = 1.0
txfDream.layer.cornerRadius = 8.0
txfDream.font = UIFont.systemFont(ofSize: 16)
// 添加工具欄
let toolbar = UIToolbar()
toolbar.sizeToFit()
let doneButton = UIBarButtonItem(
title: "完成",
style: .done,
target: self,
action: #selector(dismissKeyboard)
)
let flexSpace = UIBarButtonItem(
barButtonSystemItem: .flexibleSpace,
target: nil,
action: nil
)
toolbar.items = [flexSpace, doneButton]
txfDream.inputAccessoryView = toolbar
}
@objc private func dismissKeyboard() {
view.endEditing(true)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
接下來,我們需要一個頁面來接收並顯示夢境的解析文字與生成的圖片。
在 Controller
資料夾中,建立一個新的 Cocoa Touch Class,命名為 ResultViewController
,記得勾選 Also create XIB file
。
打開 ResultViewController.xib
,在畫布上放置一個 UITextView
用於顯示解析文字,以及一個 UIImageView
用於顯示夢境圖片,並為它們設定好約束。
打開 ResultViewController.swift
,加入最關鍵的「信箱」屬性,並連結 UI 元件。
檔案:ResultViewController.swift
import UIKit
class ResultViewController: UIViewController {
// MARK: - IBOutlet
@IBOutlet weak var imgvResult: UIImageView!
@IBOutlet weak var txfResult: UITextView!
// MARK: - Property
var interpretationText: String?
var dreamImage: UIImage?
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
title = "夢境分析結果"
// 顯示傳遞過來的文字和圖片
txfResult.text = interpretationText
imgvResult.image = dreamImage
}
}
一切準備就緒,我們回到 InputViewController.swift
,完成最後的「投遞」動作。
檔案:InputViewController.swift
(新增 IBAction)
// ...接續 InputViewController.swift ...
// MARK: - IBAcion
@IBAction func btnSent(_ sender: UIButton) {
// 取得使用者輸入的夢境描述
guard let dreamDescription = txfDream.text, !dreamDescription.isEmpty else {
// 可以在此處加入提示,提醒使用者輸入內容
return
}
// 建立「結果頁」的實例
let resultVC = ResultViewController(nibName: "ResultViewController", bundle: nil)
// 將描述文字,直接賦值給結果頁的公開屬性
resultVC.interpretationText = dreamDescription
// 明天我們也會把 AI 生成的圖片放在這裡
// resultVC.dreamImage = ...
// 將結果頁推入導航堆疊中
navigationController?.pushViewController(resultVC, animated: true)
}
// ...接續 ...
}
今天,我們成功打通了 App 內多個頁面之間的動脈。我們為 AI 夢境視覺化師鋪好了最關鍵的道路。
明天,我們將串接外部的圖像生成 AI API,將這段文字,轉化為一幅獨一無二的超現實畫作!