LoginSignup
0
1

More than 1 year has passed since last update.

PHPickerViewControllerの処理をSwift Concurrencyを使って書いた

Posted at

PHPickerViewControllerの処理をSwift Concurrencyを使って書いた。それの備忘録。
SwiftUI版で書いてるが、UIKitでも同様に書ける。

完成したもの

struct ImagePicker: UIViewControllerRepresentable {
    @Environment(\.dismiss) fileprivate var dismiss
    let select: ([UIImage]) -> Void

    func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration()
        configuration.filter = PHPickerFilter.images
        configuration.selectionLimit = 0 // maximum
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ picker: UIViewControllerType, context: Context) {
    }

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

    class Coordinator: NSObject, PHPickerViewControllerDelegate {
        private var parent: ImagePicker

        init(_ picker: ImagePicker) {
            self.parent = picker
        }

        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            Task {
                let images = await withThrowingTaskGroup(of: UIImage.self) { group in
                    for result in results {
                        group.addTask { try await self.loadImage(result: result) }
                    }
                    var images = [UIImage]()
                    while let nextResult = await group.nextResult() {
                        switch nextResult {
                        case .success(let value):
                            images.append(value)
                        case .failure(let error):
                            print(error)
                        }
                    }
                    return images
                }
                parent.select(images)
                parent.dismiss()
            }
        }

        private func loadImage(result: PHPickerResult) async throws -> UIImage {
            return try await withCheckedThrowingContinuation { continuation in
                let provider = result.itemProvider
                provider.loadObject(ofClass: UIImage.self) { image, error in
                    if let error {
                        continuation.resume(throwing: error)
                        return
                    }
                    guard let image = image as? UIImage else {
                        continuation.resume(throwing: PickerError.missingImage)
                        return
                    }
                    continuation.resume(returning: image)
                }
            }
        }
    }
}
private enum PickerError: Error {
    case missingImage
}

errorの扱い方

上記のコードではfunc loadImageでerrorがthrowされても、他のUIImageだけで成功するようにnextResultを使って処理を書いたが、1つerrorが出たら終了させて良いのであれば、下記のように書ける。

- let images = await withThrowingTaskGroup(of: UIImage.self) { group in
+ let images = try await withThrowingTaskGroup(of: UIImage.self) { group in
    for result in results {
        group.addTask { try await self.loadImage(result: result) }
    }
-    var images = [UIImage]()
-    while let nextResult = await group.nextResult() {
-        switch nextResult {
-        case .success(let value):
-            images.append(value)
-        case .failure(let error):
-            print(error)
-        }
-    }
-    return images
+    return try await group.reduce(into: [UIImage]()) { $0.append($1) }
}

今回はfunc pickerでerror処理をしたかったのでこのように書いたが、親ViewでAlertを表示させたいなどの場合は違う書き方となる。

また、loadObjectでerrorがnilでimageをUIImageでキャストできないケースがどういう時かわからなかったが、一応PickerError.missingImageを流しておいた。

if let error {
    continuation.resume(throwing: error)
    return
}
guard let image = image as? UIImage else {
    continuation.resume(throwing: PickerError.missingImage)
    return
}
continuation.resume(returning: image)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1