1

In my app I have a section of code where I need to account for a PHLivePhoto type object and convert this to a UIImage. I believe it has to do with a PHAsset, PHAssetResource, or PHImageManager but unclear how to perform the conversion. What's a good approach on how to convert from PHLivePhoto -> UIImage? Thanks for the help!

if let livePhoto = object as? PHLivePhoto {
    let livePhotoResources = PHAssetResource.assetResources(for: livePhoto)
    print("\(livePhotoResources)")
    
    // imageBucket is of type UIImage[]
    // livePhoto is of type PHLivePhoto but I don't know how to convert this to type PHAsset
    viewModel.imageBucket.append(convertImageAsset(asset: **WHAT_DO_I_INSERT_HERE**))
}

...

func convertImageAsset(asset: PHAsset) -> UIImage {
            let manager = PHImageManager.default()
            let option = PHImageRequestOptions()
            var tmpImage = UIImage()
            option.isSynchronous = true
            manager.requestImage(
                for: asset,
                   targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
                   contentMode: .aspectFit,
                   options: option,
                   resultHandler: {(result, info)->Void in
                       tmpImage = result!
                   })
            return tmpImage
        }

results in:

[<PHAssetResource: 0x28225cdc0> {
    type: photo
    uti: public.heic
    filename: IMG_5442.heic
    asset: (null)
    locallyAvailable: YES
    fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.HEIC
    width: 0
    height: 0
    fileSize: 0
    analysisType: unavailable
    cplResourceType: Unknown
    isCurrent: NO
    isInCloud: NO
}, <PHAssetResource: 0x28226bc00> {
    type: video_cmpl
    uti: com.apple.quicktime-movie
    filename: IMG_5442.mov
    asset: (null)
    locallyAvailable: YES
    fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.MOV
    width: 0
    height: 0
    fileSize: 0
    analysisType: unavailable
    cplResourceType: Unknown
    isCurrent: NO
    isInCloud: NO
}]
ninu
  • 890
  • 2
  • 10
  • 26

2 Answers2

3

The problem with the accepted answer is that fetching a PHAsset will require photo library access, and one of the main advantages of PHPickerViewController is being able to get the photos without asking for permissions, and completely avoiding all the related edge cases.

So another way of getting a live photo's image would be:

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    for result in results {
        // Live photos
        if result.itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
            result.itemProvider.loadObject(ofClass: PHLivePhoto.self, completionHandler: { livePhoto, error in
                let resources = PHAssetResource.assetResources(for: livePhoto as! PHLivePhoto)
                let photo = resources.first(where: { $0.type == .photo })!
                let imageData = NSMutableData()
                
                PHAssetResourceManager.default().requestData(for: photo, options: nil, dataReceivedHandler: { data in
                    imageData.append(data)
                }, completionHandler: { error in
                    _ = UIImage(data: imageData as Data)!
                })
            })
        }
    }
}
Alexander Borisenko
  • 1,574
  • 1
  • 12
  • 16
2

You need to use PHAsset to fetch the asset, then request image data from PHImageManager.default()

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    dismiss(animated: true)
    
    guard let assetIdentifier = results.first?.assetIdentifier  else {
        return
    }
    
    if let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil).firstObject {
        PHImageManager.default().requestImageDataAndOrientation(for: phAsset, options: nil) { [weak self] data, _, _, _ in
            if let data = data, let image = UIImage(data: data) {
                self?.viewModel.imageBucket.append(image)
            }
        }
    }
}

To get assetIdentifier, you need to create PHPickerConfiguration object using the shared photo library. Creating a configuration without a photo library provides only asset data, and doesn't include asset identifiers.

var configuration = PHPickerConfiguration(photoLibrary: .shared())
// Set the filter type according to the user’s selection. .images is a filter to display images, including Live Photos.
configuration.filter = .images
// Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
configuration.preferredAssetRepresentationMode = .current
// Set the selection limit.
configuration.selectionLimit = 1
    
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
Bobby
  • 176
  • 1
  • 4
  • Thanks for the response but you did not explain how you're getting [assetIdentifier] here. Please do elaborate more in your response. thank you! – ninu Jan 23 '22 at 21:45
  • Hi @ninu I've updated the answer :) – Bobby Jan 23 '22 at 23:27
  • 1
    Hey @Bobby your code makes sense in theory. However, when the picker shows up in my app and I select a Live Photo then click "Add" in the top right corner, the picker(picker: results:) function executes the guard let assetIdentifier and the return fires b/c results.first?assetIdentifier is nil. Any ideas? – ninu Jan 24 '22 at 00:27
  • I even added `for result in results { print("The asset identifier is: \(String(describing: result.assetIdentifier))")}` before your guard statement and this always shows **nil** no matter which Live Photo is chosen. – ninu Jan 24 '22 at 00:33
  • 2
    Please check how you initialize your picker configuration, it should be like that: `var configuration = PHPickerConfiguration(photoLibrary: .shared())` `PHPickerConfiguration` object should be created using the shared photo library. Creating a configuration without a photo library provides only asset data, and doesn't include asset identifiers. – Bobby Jan 24 '22 at 10:59
  • 1
    You are absolutely correct! I was using the boilerplate WWDC2020 demo code and they had initialized with PHPickerConfiguration() without the parameter of photoLibrary: .shared() and adding it solved the problem! How did you know to add the .shared() because that was not obvious from the documentation: https://developer.apple.com/documentation/photokit/phpickerconfiguration/3616114-init – ninu Jan 24 '22 at 16:00
  • Yeah, that's really not obvious. Here is where I read about it: https://developer.apple.com/documentation/photokit/selecting_photos_and_videos_in_ios – Bobby Jan 25 '22 at 13:58