PHAsset: Saving a new image with metadata
Asked Answered
A

2

7

I'm using a UIImagePickerController to allow the user to take a picture with the camera. When the delegate's called, I want to save the image to the photo library/camera roll, which I do using PHAssetChangeRequest. The issue is the documentation says I can use the dictionary from UIImagePickerControllerMediaMetadata to include that as metadata, but PHAssetChangeRequest doesn't seem to have a parameter to do this. How can I include this metadata when saving? Thanks!

Armidaarmiger answered 30/11, 2015 at 2:34 Comment(3)
Have you figured out a way to do this?Marou
Unfortunately not. The docs are either very opaque or wrong.Armidaarmiger
I know I can't believe how bad this library is, not up to Apple' standards at allMarou
K
1

You can use PHAssetCreationRequest. In order to use PHAssetCreationRequest, you will merge imageData and metadata, then write the imageDataWithMetadata to the album. Like this:

if let imageDataWithMetadata = self.writeMetadata(metadata, into: imageData) {
    self.saveImageDataForiOS9(imageDataWithMetadata)
}

func saveImageDataForiOS9(_ data: Data) {
    var newImageIdentifier: String!

    PHPhotoLibrary.shared().performChanges({
        if #available(iOS 9.0, *) {
            let assetRequest = PHAssetCreationRequest.forAsset()
            assetRequest.addResource(with: .photo, data: data, options: nil)
            newImageIdentifier = assetRequest.placeholderForCreatedAsset!.localIdentifier
        } else {
            // Fallback on earlier versions
        }
    }) { (success, error) in
        DispatchQueue.main.async(execute: {
            if success, let newAsset = PHAsset.fetchAssets(withLocalIdentifiers: [newImageIdentifi
                // ...
            } else {
                // ...
            }
        })

    }
}

For iOS 8, you can use ALAssetsLibrary to saving a new image with metadata:

func saveImageDataForiOS8(_ imageData: Data, _ metadata: Dictionary<AnyHashable, Any>?) {
    let library = ALAssetsLibrary()
    library.writeImageData(toSavedPhotosAlbum: imageData, metadata: metadata, completionBlock: { (newURL, error) in
        if let _ = error {
            // ...
        } else {
            if let newAsset = PHAsset.fetchAssets(withALAssetURLs: [newURL!], options: nil).firstObject {
                // ...
            }
        }
    })
}

So, finally you will have code like this:

func saveImage(_ imageData: Data, metadata: Dictionary<AnyHashable, Any>?) {
    if #available(iOS 9.0, *) {
        if let metadata = metadata {
            if let imageDataWithMetadata = self.writeMetadata(metadata, into: imageData) {
                self.saveImageDataForiOS9(imageDataWithMetadata)
            } else {
                self.saveImageDataForiOS9(imageData)
            }
        } else {
            self.saveImageDataForiOS9(imageData)
        }
    } else {
        self.saveImageDataForiOS8(imageData, metadata)
    }
}

func saveImageDataForiOS8(_ imageData: Data, _ metadata: Dictionary<AnyHashable, Any>?) {
    let library = ALAssetsLibrary()
    library.writeImageData(toSavedPhotosAlbum: imageData, metadata: metadata, completionBloc
        if let _ = error {
            // ...
        } else {
            if let newAsset = PHAsset.fetchAssets(withALAssetURLs: [newURL!], options: nil).
                // ...
            }
        }
    })
}

func saveImageDataForiOS9(_ data: Data) {
    var newImageIdentifier: String!

    PHPhotoLibrary.shared().performChanges({
        if #available(iOS 9.0, *) {
            let assetRequest = PHAssetCreationRequest.forAsset()
            assetRequest.addResource(with: .photo, data: data, options: nil)
            newImageIdentifier = assetRequest.placeholderForCreatedAsset!.localIdentifier
        } else {
            // Fallback on earlier versions
        }
    }) { (success, error) in
        DispatchQueue.main.async(execute: {
            if success, let newAsset = PHAsset.fetchAssets(withLocalIdentifiers: [newImageId
                // ...
            } else {
                // ...
            }
        })

    }
}

internal func writeMetadata(_ metadata: Dictionary<AnyHashable, Any>, into imageData: Data) 
    let source = CGImageSourceCreateWithData(imageData as CFData, nil)!
    let UTI = CGImageSourceGetType(source)!

    let newImageData = NSMutableData()
    if let destination = CGImageDestinationCreateWithData(newImageData, UTI, 1, nil) {
        CGImageDestinationAddImageFromSource(destination, source, 0, metadata as CFDictionar
        if CGImageDestinationFinalize(destination) {
            return newImageData as Data
        } else {
            return nil
        }
    } else {
        return nil
    }
}
Kwarteng answered 2/9, 2017 at 16:37 Comment(3)
I am not sure CGImageDestinationAddImageFromSource works like that. At least according to the docs, it does not.Reinert
@DagÅgren I think CGImageDestinationAddImageFromSource works well. I‘ve used this method for several months in this library: github.com/zhangao0086/DKImagePickerController/blob/develop/…Kwarteng
This is what I was looking for. Saved my weekCoax
C
0

From what I can understand by reading the source, what Apple means by Metadata is the properties of the asset.

open var pixelWidth: Int { get }
open var pixelHeight: Int { get }
open var creationDate: Date? { get }
open var modificationDate: Date? { get }
open var location: CLLocation? { get }
open var duration: TimeInterval { get }
open var isHidden: Bool { get }

The others metadata can be found in the image, by accessing the CGImage properties.

Carotid answered 2/9, 2017 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.