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!
PHAsset: Saving a new image with metadata
Asked Answered
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 all –
Marou
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
}
}
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 week –
Coax
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.
© 2022 - 2024 — McMap. All rights reserved.