🎃
PHPickerViewControllerの処理をSwift Concurrencyを使って書いた
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)
Discussion