I use a PHPickerViewController
to select profile images and want the first photo selected to be a user's hero image.
Whenever I return the selected results, the order in which I selected is not reflected.
I've double checked my config and looked at existing SO answers, but no solution has worked and would appreciate any guidance that does not involve using a 3rd party!
Apple's documentation states by simply setting the .selection
property to .ordered
, a user's selected order should be respected, but it does not...
//Setup code
var config = PHPickerConfiguration()
config.selectionLimit = 3
config.filter = .images
config.selection = .ordered
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
//Delegate handler
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard !results.isEmpty else {
picker.dismiss(animated: true)
return
}
self.photos = []
var tempImages: [Int: UIImage] = [:]
let dispatchGroup = DispatchGroup()
for (index, result) in results.enumerated() {
dispatchGroup.enter() // Enter the group
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] object, error in
defer { dispatchGroup.leave() }
guard let self = self else { return }
if let image = object as? UIImage {
tempImages[index] = image
}
}
}
dispatchGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
for index in 0..<tempImages.keys.count {
if let image = tempImages[index] {
self.photos?.append(image)
}
}
}
picker.dismiss(animated: true)
}
This won't answer the question of the order in which the results
array arrives, but you can greatly simplify your code and be certain of getting your images in the same order as the results
array by introducing this extension to NSItemProvider:
extension NSItemProvider {
@objc func loadImage() async throws -> UIImage {
enum ImageLoadingError: Error {
case couldNotConvertDataToImage
case unknown
}
return try await withCheckedThrowingContinuation { continuation in
self.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, error in
if let data {
if let image = UIImage(data: data) {
continuation.resume(returning: image)
} else {
continuation.resume(throwing: ImageLoadingError.couldNotConvertDataToImage)
}
} else {
continuation.resume(throwing: error ?? ImageLoadingError.unknown)
}
}
}
}
}
This will allow you to simply loop in a very ordinary way thru the results
, in order, without all the hair-raising complication of the dispatch group:
var images = [UIImage]()
for result in results {
let provider = result.itemProvider
do {
let image = try await provider.loadImage()
images.append(image)
} catch {
print(error.localizedDescription)
}
}
(You'll have to get into an async context to make that call, obviously; but that's easy.) The output images
is guaranteed to match the order of the results
and the code is far simpler and can be reasoned about.