The Util Designer
SwiftUI 使用UIViewControllerRepresentable protocol存取相片
xcode 13.4.1, swift 5.5, iOS 15.4
2022-09-05
SwiftUI雖然已經提供了很多內建的元件,但SwiftUI還沒有UIKit成熟,像選擇相片在iOS 15是還沒有內建的(不過iOS 16就已提供),所以有些時候需要借助UIKit來實現,要做這個工作,就要通過SwiftUI提供的UIViewControllerRepresentable protocol來與UIKit中的視圖控制器 (View Controller)作橋樑溝通。
1. 創建一個符合UIViewControllerRepresentable protocol struct,這個就可以像其他內建的View來使用,這個struct主要要實作 makeUIViewController 和 updateUIViewController , makeUIViewController 主要是用來創建UIViewController , updateUIViewController 主要是用來更新 UIViewController,框架如下:
import UIKit
import SwiftUI

struct ImagePickerView : UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> UIImagePickerController {
        // create UIImagePickerController
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        // update UIImagePickerController
    }
}
2. 首先實作makeUIViewController,並反回一個 UIImagePickerController :
import UIKit
import SwiftUI

struct ImagePickerView : UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        return imagePicker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        //update UIImagePickerController
    }
}
3. 然後像其他內建的元件使用就可以,以下實作是按完圖片後,會彈出圖片選擇器,並選擇圖片,程式如下:
import SwiftUI

struct UIViewControllerRepresentableExample : View {
    
    @State private var isShowImagePickerView : Bool = false
    
    @State private var userPickedImage: Image? = Image("cat6")

    var body: some View {
        VStack {
            userPickedImage!
                .resizable()
                .scaledToFit()
                .onTapGesture {
                    isShowImagePickerView = true
                }
        }
        .sheet(isPresented: $isShowImagePickerView) {
            ImagePickerView()
        }
    }
    
}
4. 到目前為止已可以彈出圖片選擇器,但選完圖片後,App中顯示的圖片並沒有改變,為這把選中的圖片顯示在Image元件中,需要一個協調器,用這個協調器把得到選中的圖片並傳給Image元件,實現如下:
import UIKit
import SwiftUI

struct ImagePickerView : UIViewControllerRepresentable {

    @Environment(\.presentationMode) var isPresented

    @Binding var selectedImage: Image?

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        imagePicker.delegate = context.coordinator
        return imagePicker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        //update UIImagePickerController
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(picker: self)
    }

}

class Coordinator : NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    var picker: ImagePickerView
    
    init(picker: ImagePickerView) {
        self.picker = picker
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let selectedUIImage = info[.originalImage] as? UIImage else { return }
        self.picker.selectedImage = Image(uiImage: selectedUIImage)
        self.picker.isPresented.wrappedValue.dismiss()
    }
    
}
import SwiftUI

struct UIViewControllerRepresentableExample : View {
    
    @State private var isShowImagePickerView : Bool = false
    
    @State private var userPickedImage: Image? = Image("cat6")

    var body: some View {
        VStack {
            userPickedImage!
                .resizable()
                .scaledToFit()
                .onTapGesture {
                    isShowImagePickerView = true
                }
        }
        .sheet(isPresented: $isShowImagePickerView) {
            ImagePickerView(selectedImage: $userPickedImage)
        }
    }
    
}