How can I convert a PHLivePhoto to UIImage?

1.5k Views Asked by At

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
}]
2

There are 2 best solutions below

7
Bobby On BEST ANSWER

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)
0
Alexander Borisenko On

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)!
                })
            })
        }
    }
}