先前我們曾探討 UIViewRepresentable
的用法,並展示了如何整合 UITextView 到 SwiftUI 專案中。雖然我們可以使用 UIViewRepresentable
協定包裝 UIKit 視圖 (View),但是視圖控制器 (View Controller) 呢?你可能需要在 App 中使用相機或存取使用者的相簿。那麼,如何將 UIImagePickerController
類別整合到 SwiftUI 視圖中呢?
在本教程中,我們將使用 UIViewControllerRepresentable
協定帶你完成整合作業。它與 UIViewRepresentable
協定非常相似,但是 UIViewControllerRepresentable
是為包裝 UIKit 視圖控制器而設計的。如果你已經閱讀了 UIViewRepresentable
的教程,就應該對以下將要討論的步驟非常熟悉。
使用 UIViewControllerRepresentable
基本上,要將 UIImagePickerController
整合到 SwiftUI 專案中,我們可以使用 UIViewControllerRepresentable
協定,來包裝控制器並實現所需的方法:
struct ImagePicker: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
// Return an instance of UIImagePickerController
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
這個協定有兩個需要遵從的方法。當初始化 ImagePicker
時,就會調用 makeUIViewController
方法。在該方法中,你需要實例化 (instantiate) UIImagePickerController
,並配置其初始狀態。另外,在 App 狀態有所更改而影響 ImagePicker
時,就會調用 updateUIViewController
方法。你可以實作這個方法,來更新 UIImagePickerController
的配置。如果沒有要更新的內容,你也可以將該方法留空。
在 SwiftUI 中創建 ImagePicker
與往常一樣,我喜歡通過構建範例專案來說明 API。讓我們使用 Single View Application 模板 (template),創建一個名為 SwiftUIImagePicker 的新專案。請在 User Interface 選項中選擇 SwiftUI。
接下來,我們將為 ImagePicker
創建一個新檔案。右鍵單擊專案 navigator 中的 SwiftUIImagePacker 文件夾,然後選擇 New File…,再選擇 Swift File 模板。 首先,我們需要匯入 UIKit 和 SwiftUI 框架:
import UIKit
import SwiftUI
接下來,創建 ImagePicker
結構 (struct),並採用 UIViewControllerRepresentable
協定:
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.allowsEditing = false
imagePicker.sourceType = sourceType
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
UIImagePickerController
類別允許你訪問相簿,並使用內建相機。在上面的程式碼中,我們為此宣告一個 sourceType
變數 (variable)。在預設情況下,它會設置為打開使用者的相簿。在 makeUIViewController
方法中,我們實例化 UIImagePickerController
的實例,並配置它的 source type。
使用 ImagePicker 在 SwiftUI 視圖中加載相簿
現在我們已經創建了 ImagePicker
,下一步來看看如何在 SwiftUI 視圖中使用它吧。切換到 ContentView.swift
,並如此更新 ContentView
結構:
struct ContentView: View {
@State private var isShowPhotoLibrary = false
@State private var image = UIImage()
var body: some View {
VStack {
Image(uiImage: self.image)
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity)
.edgesIgnoringSafeArea(.all)
Button(action: {
self.isShowPhotoLibrary = true
}) {
HStack {
Image(systemName: "photo")
.font(.system(size: 20))
Text("Photo library")
.font(.headline)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(20)
.padding(.horizontal)
}
}
}
}
我們有一個狀態變數用來控制相簿的出現時機,而另一個狀態變數用於所選圖像,在預設情況下,所選圖像設置為空白。在視圖中,我們有一個可以更改 isShowPhotoLibrary
狀態的按鈕。如果你正確地編寫了程式碼,應該會在預覽中看到以下內容:
要使用 ImagePicker
加載相簿,請將 .sheet
修飾符 (modifier) 加到 VStack
中:
.sheet(isPresented: $isShowPhotoLibrary) {
ImagePicker(sourceType: .photoLibrary)
}
在閉包中,我們創建了 ImagePicker
,並在使用者點擊 Photo Library 按鈕時顯示相簿,在預覽畫布 (preview canvas) 中運行 App,應該可以打開相簿。
讓 Coordinator 採用 UIImagePickerControllerDelegate 協定
目前,內容視圖對所選圖片一無所知。你在相簿中選擇了一張照片後,App 只會關閉相簿視圖並返回主螢幕。
如果你有曾經用過 UIImagePickerController
,就會知道必須採用兩個委託 (delegate):UIImagePickerControllerDelegate
和 UINavigationControllerDelegate
,以便與 UIImagePickerController
進行交互。當使用者從相簿中選擇照片時,將調用委託的 imagePickerController(_:didFinishPickingMediaWithInfo:)
方法。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
}
實作該方法後,我們可以從方法的參數中獲取選定的照片。要在 ImagePicker
中採用該協定,我們需要實作 makeCoordinator
方法,並提供一個 Coordinator
實例,這個 Coordinator
充當控制器的委託與 SwiftUI 之間的一道橋樑。
現在,讓我們回到 ImagePicker.swift
,並宣告兩個變數:
@Binding var selectedImage: UIImage
@Environment(\.presentationMode) private var presentationMode
顧名思義,selectedImage
變數用於儲存所選圖像。使用者選擇照片後,我們需要關閉相簿。為此,我們使用了 presentationMode
變數。稍後,我們可以調用 presentationMode.wrappedValue.dismiss()
來關閉視圖。
接下來,在 ImagePicker
內部創建 Coordinator
類別:
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
parent.selectedImage = image
}
parent.presentationMode.wrappedValue.dismiss()
}
}
Coordinator
類別採用 UIImagePickerControllerDelegate
協定,並實作 imagePickerController(_:didFinishPickingMediaWithInfo:)
委託方法。當選擇好一個圖像時,就會調用該方法。在該方法中,我們取回選定的圖像,然後關閉相簿。
init
方法會接受 ImagePicker
的實例,以便我們將選定的圖像傳遞給它,並使用其 presentationMode
來關閉視圖。
現在我們已經準備好 Coordinator
類別了,下一步是創建 makeCoordinator
方法,並回傳 Coordinator
的實例:
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
最後,我們需要將 coordinator 分配給 UIImagePickerController
的委託。在 makeUIViewController
方法中,插入以下程式碼:
imagePicker.delegate = context.coordinator
在測試更改之前,讓我們切換到 ContentView.swift
,並更新 .sheet
修飾符中的程式碼:
.sheet(isPresented: $isShowPhotoLibrary) {
ImagePicker(sourceType: .photoLibrary, selectedImage: self.$image)
}
我們需要傳遞 image
的綁定,讓 ImagePicker
可以傳遞使用者選擇的圖像進行顯示。現在,在模擬器或預覽畫布中運行 App,現在應該可以顯示所選圖像。
在 SwiftUI 中使用相機
ImagePicker
具有足夠的靈活性以支援內建相機。如果你想在 SwiftUI App 中打開相機拍照,可以將 sourceType
從 .photoLibray
更改為 .camera
,如下所示:
ImagePicker(sourceType: .camera, selectedImage: self.$image)
在 iOS 中,使用者必須明確授予每個 App 訪問相機的權限。所以,除了上述更改之外,我們還需要編輯 Info.plist
文件,並指定 App 需要使用內置相機的原因。
你無法使用模擬器測試這一點。但是,如果你有一台 iPhone,就可以將 App 配置到設備上進行測試。
總結
在這篇文章中,我們介紹了 UIViewControllerRepresentable
協定,它是 UIKit 視圖控制器和 SwiftUI 視圖之間的一道橋樑。我們利用這個協定,將 UIImagePickerController
整合到了 SwiftUI 專案中,從而允許 App 訪問相簿和設備的相機。
SwiftUI 仍然是一個非常新的框架,因此並非所有 UIKit 元件可用於都在這個框架中。如果你在 SwiftUI 中找不到原生 UI 元件,就可以應用這個技巧,將 UIKit 元件引入 SwiftUI 專案。
你可以在 GitHub 上下載完整專案作參考。
聯絡方式:
電郵: [email protected]
FB : https://www.facebook.com/yishen.chen.54
Twitter : https://twitter.com/YeEeEsS
原文: How to Access Photo Library and Use Camera in SwiftUI