iosswiftphotokit

PhotoKit: get full resolution offline asset preview, when the requested asset is in iCloud


When loading images from the photo library via PhotoKit, it can happen that the image is not downloaded from iCloud. In this case, a low-resolution "placeholder" version of that image is available locally if you browse it in the photos App:

enter image description here

However, when I try to get the same photo to display in an app, the photo that I get from PHAssetManager will be at maximum 120 units wide (or high). This looks very blurry and is completely unusable except for a thumbnail.

Blurry image in app

I have already tried

I wanted to ask if anyone has come across this issue and maybe worked around it.

The behavior can be easily reproduced with some PhotoKit example app, for example from these course materials. This is the code I used to produce the below image:


extension UIImageView {
  func fetchImageAsset(_ asset: PHAsset?, targetSize size: CGSize, contentMode: PHImageContentMode = .aspectFill, completionHandler: ((Bool) -> Void)?) {

    let options = PHImageRequestOptions()
    options.deliveryMode = .opportunistic
    options.resizeMode = .none
    options.isNetworkAccessAllowed = false

    // 1
    guard let asset = asset else {
      completionHandler?(false)
      return
    }
    // 2
    let resultHandler: (UIImage?, [AnyHashable: Any]?) -> Void = { image, info in
      if let image = image {
        self.image = image
      }
      completionHandler?(true)
    }
    // 3
    PHImageManager.default().requestImage(
      for: asset,
      targetSize: size,
      contentMode: contentMode,
      options: options,
      resultHandler: resultHandler)
  }
}

Solution

  • Counter-intuitively, PHImageManager will return higher-resolution images when the requested target size is below a certain threshold.

    This gives us an ugly albeit functioning workaround that can be implemented as follows.

    First, load the image as usual.

    If the loading is completed (PHImageResultIsDegradedKey is 0) but PHImageResultIsInCloudKey is 1, load the image again with the targetSize 360x360, which I have found a suitable value by experimentation. Here it is important to use the opportunistic delivery mode and .none for the resizeMode. Other values for these settings do not work.

    Here is an excerpt of my code. The continuation.yield calls carry information about the process to the outside, async world.

    func imageRequestOptions(deliveryMode: PHImageRequestOptionsDeliveryMode,
                             progressHandler: PHAssetImageProgressHandler? = nil) -> PHImageRequestOptions {
        let opts = PHImageRequestOptions()
        opts.resizeMode = .none
        opts.isSynchronous = false
        opts.isNetworkAccessAllowed = networkAccessAllowed()
        opts.deliveryMode = deliveryMode
        opts.progressHandler = progressHandler
        return opts
    }
    ...
    let targetSizeForOfflinePhotos = CGSize(width: 360, height: 360)
    if metadata?[PHImageResultIsDegradedKey] as? Int == 0
        && metadata?[PHImageResultIsInCloudKey] as? Int == 1  {
        continuation.yield(.internetAccessRequired)
        let requestId = assetManager
            .requestImage(
                for: asset,
                targetSize: targetSizeForOfflinePhotos,
                contentMode: .aspectFill,
                options:
                    imageRequestOptions(
                        deliveryMode: .opportunistic
                    )
            ) { image, metadata in
                if let image = image {
                    continuation.yield(.imageAvailable(.uiImage(image)))
                } else {
                    continuation.finish(throwing: PhotoLibraryError.assetNotFound)
                }
            }
        continuation.onTermination = { _ in
            Task {
                assetManager.cancelImageRequest(requestId)
            }
        }
    

    There might still be a correct way to implement image loading for offline scenarios. Unfortunately as of now, this hack is the only thing that I can find and it improves user experience by a vast margin in my case.

    It seems that I am also not the only one having trouble with this: I downloaded multiple popular apps that deal with library photos, and they all suffer from the same issue I described in the question.