`PHPickerResult.itemProvider.canLoadObject(ofClass: UIImage.self)` returns `false` on iOS 17

191 views Asked by At

I'm using PHPickerViewController. In the delegate method, I load the images like this:

results.forEach {
    if $0.itemProvider.canLoadObject(ofClass: UIImage.self) {
        $0.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
            guard let image = image as? UIImage else { return }
        }
    }
}

This code works on iOS 12~16, but on iOS 17, from time to time, the itemProvider.canLoadObject method straight up returns false.

At first, I found this issue on iOS 17.0.3, and I upgraded the OS, and it's gone. But later bug reports suggest that it also appears on iOS 17.1.1.

I haven't found much discussion about it though. I did find some similar issues:

The later one is on ionic but it appears to be the same. In that thread, there was a workaround.

I didn't test it since the PHAsset.fetchAssets code in it would require photo library permission. This could break our app's user experience, since the picker itself wouldn't require any permission to popup and it shows all the photos, but the PHAsset would ask for it when you finished the selection. What's even worse is when we have limited access to the photo library, we might not even be able to load the photo that's selected by the user but is limited to the app.

Upgraded the OS version from 17.0.3 to 17.1, and it was gone. But later found the same issue on iOS 17.1.1.

1

There are 1 answers

0
iMoeNya On

Turns out loadObject(ofClass: UIImage.self) won't work for RAW photos on iOS 17.

Again, this does work on iOS 16.

I ended up changing my code to this:

func loadUIImageFromPickerResult(
    _ result: PHPickerResult,
    completion: @escaping (Result<UIImage, Swift.Error>) -> Void
) -> Progress {
    let provider = result.itemProvider
    if provider.canLoadObject(ofClass: UIImage.self) {
        return startLoadingImage()
    } else {
        return startLoadingDataThenConvertToImage()
    }
    
    func startLoadingImage() -> Progress {
        provider.loadObject(
            ofClass: UIImage.self
        ) { image, error in
            DispatchQueue.main.async {
                if let image = image as? UIImage {
                    completion(.success(image))
                } else {
                    completion(.failure(error ?? Picker.Error.nothingFromApple))
                }
            }
        }
    }
    
    func startLoadingDataThenConvertToImage() -> Progress {
        loadDataFromPHPickerResult(result) { dataResult in
            DispatchQueue.main.async {
                do {
                    let data = try dataResult.get()
                    guard let image = UIImage(data: data) else {
                        completion(.failure(Picker.Error.dataIsNotImage))
                        return
                    }
                    completion(.success(image))
                } catch {
                    completion(.failure(error))
                }
            }
        }
    }
}

func loadDataFromPHPickerResult(
    _ result: PHPickerResult,
    completion: @escaping (Result<Data, Swift.Error>) -> Void
) -> Progress {
    result.itemProvider.loadDataRepresentation(
        forTypeIdentifier: UTType.image.identifier
    ) { data, error in
        DispatchQueue.main.async {
            if let data = data {
                completion(.success(data))
            } else {
                completion(.failure(error ?? Picker.Error.nothingFromApple))
            }
        }
    }
}