SwiftUIでPHPhotoPickerを使用する

# この記事は何？
PHPhotoPickerをSwiftUIで使用するアプリを作成します。

![スクリーンショット 2021-04-29 13.15.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/44b07d1c-7fd5-c4c2-f7d2-9fcdf6e4e2c9.png)


###実行環境
Swift 5.3
Xcode 12.5
macOS 11.3

# Photosフレームワーク
「デバイスに保存された写真」を取得するための、プライバシーに配慮した全く新しいフレームワークです。


# ハンズオン
順を追って、アプリを作成していきます。

###基本の画面
基本画面には「写真を表示するための枠」と「写真ピッカーを開くためのボタン」を配置します。
まずは、「写真を表示する枠」のビューを作成しておきます。

````Swift:PhotoFrame.swift
import SwiftUI

struct PhotoFrame: View {
    @Binding var image: Image?

    var body: some View {
        if let image = image {
            image
                .resizable()
                .aspectRatio(contentMode: .fit)
        } else {
            Image(systemName: "photo")
                .font(.title)
                .foregroundColor(.gray)
        }
    }
}
````

次に、「写真ピッカーを開くためのボタン」を作成します。

````Swift:ShowPickerButton.swift
import SwiftUI

struct ShowPickerButton: View {
    @Binding var isShowLibrary: Bool
    
    var body: some View {
        Button(action: {
            isShowLibrary = true
        }, label: {
            Text("Choose Photo")
                .font(.title2)
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10.0)
        })
    }
}
````

こららのビューを基本画面に配置します。

````Swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var isShowPhotoLibrary = false
    @State var image: Image?
    @EnvironmentObject var classifier: ImageClassifier
    
    var body: some View {
        ZStack {
            PhotoFrame(image: $image)
            VStack {
                Spacer()
                Text(classifier.classificationLabel)
                ShowPickerButton(isShowLibrary: $isShowPhotoLibrary)
            }
        }
        .sheet(isPresented: $isShowPhotoLibrary, content: {
            PhotoPicker(isPresented: $isShowPhotoLibrary,
                        seledtedImage: $image)
        })
    }
}
````

###写真ピッカーのビューを作成する 
UIKitのビューコントローラをSwiftUIで使用するは、ViewControllerRepresentableプロトコルに適合した構造体でラップします。

````Swift:PhotoPicker.swift
import SwiftUI
import PhotosUI

struct PhotoPicker: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    @Binding var seledtedImage: Image?
    
    func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration()
        configuration.filter = .images

        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator

        return picker
    }

    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        // do nothing.
    }
}
````

### PhotoPicker型を定義する
デリゲートメソッドを介してPhotoPickerViewControllerとやり取りするには、仲介役となるCoordinatorクラスを定義する必要があります。

````Swift:PhotoPicker.swift

````

### コーディネーターを定義する
このクラスは、PhotoPicker構造体のネスト型として定義します。

````Swift:Coordinatorクラス
import SwiftUI
import PhotosUI

struct PhotoPicker: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    @Binding var seledtedImage: Image?

    func makeUIViewController(context: Context) -> PHPickerViewController {...}

    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        // do nothing.
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: PHPickerViewControllerDelegate {
        private let parent: PhotoPicker

        init(_ parent: PhotoPicker) {
            self.parent = parent
        }

        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            parent.isPresented = false

            let itemProvider = results.first?.itemProvider  // itemProvider is NSItemProvider? Type.

            if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
                itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                    if let uiImage = image as? UIImage {
                        self.parent.seledtedImage = Image(uiImage: uiImage)
                    }
                }
            }
        }
    }
}
````

#最後に
WWDC2021で、PHPickerViewがSwiftUIネイティブになったらいいなー。