Integrating UIKit with SwiftUI, 初探
由於 SwiftUI 是從 iOS 13.0 才開始導入(目前版本為 iOS 14.4),相較於遠從 iOS 2.0 就已經存在的 UIKit 來說,簡直就像是新生兒一般的存在。因此,某些時候我們會希望能直接利用 UIKit 或其他較成熟的 Apple framework 所提供的程式碼來進行開發。
在進入正題之前,需對 UIKit 有一些基本的認識:
- UIKit 的
UIView
class 是 layout 底下所有 views 的 parent class(views 包含 sliders、buttons…)。 - 而 UIKit 的
UIViewController
class 則是負責將 view 實現的程式碼,它和 UIView 一樣都擁有許多 subclass 負責不同的任務。 - UIKit 使用的 design pattern 稱為 delegation(委派),當 view 的狀態改變時就由它接手處理。
接下來,將以「使用者從 photo library(照片圖庫)挑選圖片」來進行說明與示範。這個動作將會使用 UIKit 底下的UIImagePickerController
,和兩個 delegate protocol - 分別是UINavigationControllerDelegate
和UIImagePickerControllerDelegate
,但 SwiftUI 無法直接使用它們。
首先,UIImagePickerController
是UIViewController
的一個 subclass,所以將它包裹(wrap)在 struct 底下時,struct 必須要符合UIViewControllerRepresentable
protocol(此處將 custom struct 取名為 “ImagePicker”)。
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
// more code
}
根據如下的 SwiftUI 定義,一旦符合UIViewControllerRepresentable
便可將其使用於 UI。
protocol UIViewControllerRepresentable : View where Self.Body == Never
接著,在 ImagePicker 底下創建兩個必要的方法:makeUIViewController()
和updateUIViewController()
。在makeUIViewController()
裡必須創建一個所需的 UIImagePickerController 並回傳。至於updateUIViewController()
裡則可留白,暫無需使用。
struct ImagePicker: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
// 暫且留白
}
}
補充:此處有個較便捷的方式可產生這兩個 function,就是在 struct 底下先鍵入以下這段文字,然後在 Xcode 跳出的 alert message 中點按 fix 即可。
struct ImagePicker: UIViewControllerRepresentable {
typealias UIViewControllerType = UIImagePickerController
}
完成以上步驟後,ImagePicker 算是擁有一半的功能,也就是能讓使用者從照片圖庫中選取圖片,但選完之後 SwiftUI 就不知下一步該做什麼。所以,必須接著完成前面 UIKit 基本認識中所說的第3點 - delegate。而 UIKit 所採用的 delegate 解決方案,在 SwiftUI 裡被稱為 coordinator,它是一個 object 專門負責對其綁定對象的事件做出反應。
接著,在 ImagePicker 底下創建一個 custom class,取名為 "Coordinator"。同時,新增方法 makeCoordinator()
,並在其中產生一個 Coordinator 的實例。然後,在makeUIViewController()
中,將 coordinator 指派給 picker 的 delegate。最後,SwiftUI 會檢查我們所創建的 Coordinator class 是否符合相應的 protocol(NSObject
、UINavigationControllerDelegate
、UIImagePickerControllerDelegate
),所以還必須將其補上。
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// more code
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
最後,在使用者選取完圖片時,通常 UI 便會同步關閉選取視窗,並將所選的圖片通知其接手的 view。因此,我們還必須加上 presentationMode 的 Environment 變數,和存放所選圖片的 Binding 變數。另外,在 Coordinator class 裡,也必須在初始化時通知其所對應的對象為何。
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.presentationMaode) var presentationMode
@Binding var image: UIImage?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
}
func makeCoordinator() {
Coordinator(self)
}
// ...略過程式碼
}
至此,我們創建了一個包裹著 UIImagePickerController 的 struct,並可將其儲存為獨立的 swift 檔案,如此便能將其重複使用於類似的情境中。
Comments