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!
Asked
Active
Viewed 3,346 times
7
-
Have you figured out a way to do this? – Etienne Noël Mar 03 '16 at 01:39
-
Unfortunately not. The docs are either very opaque or wrong. – livings124 Mar 03 '16 at 12:22
-
I know I can't believe how bad this library is, not up to Apple' standards at all – Etienne Noël Mar 03 '16 at 12:45
2 Answers
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
}
}

Bannings
- 10,376
- 7
- 44
- 54
-
I am not sure CGImageDestinationAddImageFromSource works like that. At least according to the docs, it does not. – Dag Ågren Jan 16 '18 at 22:12
-
@DagÅgren I think CGImageDestinationAddImageFromSource works well. I‘ve used this method for several months in this library: https://github.com/zhangao0086/DKImagePickerController/blob/develop/Sources/DKImagePickerController/DKImagePickerController.swift#L438 – Bannings Jan 17 '18 at 11:10
-
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.

Moose
- 2,607
- 24
- 23