你有沒有試過使用 SwiftUI?是不是也覺得 SwiftUI 非常易於使用?
在 UIKit 視圖中,如果我們在程式庫 (codebase) 進行雛形 (prototype) 設計時,每次都需要重新編譯和構建專案,才能在模擬器中看到結果。但在 SwiftUI 中,我們無需重新構建都可以隨時查看更改,十分方便。
我們有沒有辦法可以讓 UIKit 組件都變得如此方便?我們希望不需要花時間重新編譯和構建專案,就可以預覽或熱重載 (hot reload) UIKit 的更改。
實際上,我們是希望利用 SwiftUI 為 UIKit 視圖創建一個實時預覽系統。
實作的方法有很多,像是 @IBDesignable
、@IBInspectable
等簡單的標註 (annotation) 。
但如果你有用過這些標註,就會知道有時它會因為某些原因出現錯誤,有時可能不是最好的方法。
因此,在這篇文章中,我們會學習一個更簡單的新方法,來即時預覽我們的更改。如果你有用過 SwiftUI,可能已經知道這個方法,不過我們會添加一個小技巧,讓它可以用於 UIKit 中。
SwiftUI:PreviewProvider
PreviewProvider
是一個協定型別 (protocol type),在 Xcode 提供視圖預覽。當中包含一個名為 Preview
的關聯型別 (associated type),它是屬於 View
型別的。View
是一個 SwiftUI 視圖,我們會 return
它,來讓 PreviewProvider
顯示預覽。我們稍後會再深入探討這一點。
創建好符合這個協定的物件後,我們就可以傳遞希望在預覽中看到的視圖。之後,螢幕就會像這樣自動彈出一個分割視圖 (split view):
如果無法加載預覽,不用擔心,只需要按以下的鍵盤快捷鍵,就可以重置實時預覽:
⌘ + ⌥ + Return (Command + option + Return)
備註:如果預覽視圖的右上角出現 Resume 按鈕,請點擊該按鈕。如果按鈕沒有出現,就代表預覽內容已經是最新的。
UIKit:PreviewProvider
現在大家都了解 PreviewProvider
及它在 SwiftUI 的操作原理,那我們如何在 UIKit 中實作呢?
答案很簡單:我們只需要把 UIKit 程式庫轉換為 SwiftUI 可以理解的內容,並使用 SwiftUI PreviewProvider
來查看內容的實時預覽就可以了!讓我們來看如何實作吧!
如何轉換 UIKit 內容為 SwiftUI 內容?
其實方法很簡單,不用想得太複雜。我們只需要使用 UIViewControllerRepresentable
協定就可以了。
UIViewControllerRepresentable
就是把 UIViewController 轉換為 SwiftUI 視圖的橋樑。你猜到我們接下來要怎樣做了嗎?
實作
現在大家已經了解 SwiftUI 的 UIViewControllerRepresentable
和 PreviewProvider
,讓我們來一起動手實作吧!
UIKit Controller
首先,讓我們建立一個簡單的 UIKit Controller (UIViewController
),當中包含一些 UI 設定:
class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
@IBOutlet var titleLabel: UILabel!
@IBOutlet var confirmBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
}
private func setupView() {
imageView.layer.cornerRadius = imageView.frame.height / 2
imageView.clipsToBounds = true
imageView.layer.borderWidth = 1
imageView.layer.borderColor = UIColor.red.cgColor
titleLabel.text = "This is a test ViewController to see if we can use swiftUI previews for UIKit Controllers"
confirmBtn.setTitle("Confirm", for: .normal)
confirmBtn.backgroundColor = .cyan
confirmBtn.clipsToBounds = true
confirmBtn.layer.borderWidth = 1
confirmBtn.layer.borderColor = UIColor.blue.cgColor
confirmBtn.layer.cornerRadius = 20
}
}
如果我們執行這個 ViewController
,會得到以下的結果:
接下來,我們要建立一個 SwiftUI Preview
,讓我們可以實時看到 UIKit UI 的所有更改,不需要像之前那樣每次都重新編譯和構建專案。
把 UIViewController 轉換為 SwiftUI Controller
我們只需要在遵從 UIViewControllerRepresentable
的 Controller 中創建一個物件,讓它可以被 SwiftUI 讀取即可。
import Foundation
#if canImport(SwiftUI) && DEBUG
import SwiftUI
struct ViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
#endif
在上面的程式碼中:
canImport(SwiftUI)
是用來確保它只為支援 SwiftUI 的 iOS 版本進行編譯。DEBUG
則確保它只在除錯環境中運行。
在 MakeUIViewController(context: Context)
函式中,我們只需要實例化想在預覽中看到的 Controller 即可。
在上面的程式碼中,我就實例化了 Main
Storyboard。
利用 PreviewProvider 預覽 UIViewController
我們已經把 UIViewController
轉換為 SwiftUI Controller,只需要為 PreviewProvider
添加多幾行程式碼就完成了。
在 #endif
之前添加以下程式碼:
struct ViewController_Preview: PreviewProvider {
static var previews: some View {
ViewControllerRepresentable()
}
}
完整的程式碼應該是這樣的:
import Foundation
#if canImport(SwiftUI) && DEBUG
import SwiftUI
struct ViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
struct ViewController_Preview: PreviewProvider {
static var previews: some View {
ViewControllerRepresentable()
}
}
#endif
現在,我們已經有了完整的程式庫,可以隨時查看 UIKit UIViewController
的實時預覽。你可以試試修改一下 UI,就會看到即時的改變。我們可以跟繁瑣的重構步驟說再見了!
這篇文章到此為止,謝謝你的閱讀。你可以在 GitHub 上參閱完整的程式碼。