I have successfully saved an image to Photo Library using Photo Framework API's after user selected that image from library using UIImagePickerController.
As I am capturing image inside my app, I have to take care of the metadata. I could save location & creationDate to the newly captured image and saved it into library.
But the problem is that I want to attach a ton of other properties, I mean I want to attach EXIF & TIFF properties.
There are a lot of properties in EXIF like this,
kCGImagePropertyExifDateTimeOriginal
kCGImagePropertyExifMaxApertureValue
kCGImagePropertyExifSpatialFrequencyResponse
kCGImagePropertyExifLensModel
How am I supposed to get these values from the current device and attach it to image and save it to Photo Library?
Here is the complete code which I am using to save an Image to Photo Library after taking it from UIImagePickerController. I would like to put attaching metadata lines somewhere in this function before saving it to Photo Library.
//MARK: Saving an Image to PhotoLibrary and taking back the PHAsset
class func savingThis(image : UIImage, completion : (asset : PHAsset?) -> ())
{
var localIdentifier : String?
let imageManager = PHPhotoLibrary.sharedPhotoLibrary()
imageManager.performChanges({ () -> Void in
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
request.location = LocationManager.sharedInstance.locationManager.location
request.creationDate = NSDate()
// Add more properties here - somewhere EXIF, TIFF....
if let properAsset = request.placeholderForCreatedAsset {
localIdentifier = properAsset.localIdentifier
}
else {
completion(asset: nil)
}
}, completionHandler: { (success, error) -> Void in
if let properLocalIdentifier = localIdentifier {
let result = PHAsset.fetchAssetsWithLocalIdentifiers([properLocalIdentifier], options: nil)
if result.count > 0 {
completion(asset: result[0] as? PHAsset)
}
else {
completion(asset: nil)
}
}
else {
completion(asset: nil)
}
})
}
Is this a dumb question? I don't have to use Photos Framework if there is another way to attach metadata, whatever way, I just wanna attach it. I don't know. Let me know your suggestions?
Related
I'm developing Cloud based Chatting app (like Facebook Messenger), In that We want to include feature like auto download (and store) photo-video to Photo Library.
I can store image or video via following code to Photo Library once.
func savePhoto() {
if let assetCollection = fetchAssetCollectionForAlbum() {
PHPhotoLibrary.shared().performChanges {
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
} completionHandler: { (flag, error) in
if error == nil {
HapticHelper.shared.generate(feedbackType: .notificationSuccess)
}
else {
HapticHelper.shared.generate(feedbackType: .notificationError)
}
}
}
}
But When user choose to auto download (and store) photo-video to Photo Library then How can I identify that this image is already stored in library previously?
May some kind of identifier or something else which tells me that from this URL path image is already stored or not.
I have tried multiple ways to get the location of an image which took through the UIImagePickerController Camera.
What I want to achieve is that, I want to select an image using UIImagePickerController Camera and I have to save it into Photo Library so that only I can take back the PHAsset from it and also the location associated with it.
//MARK: Saving an Image to PhotoLibrary and taking back the PHAsset
class func savingThis(image : UIImage, completion : (asset : PHAsset?) -> ())
{
var localIdentifier : String?
let imageManager = PHPhotoLibrary.sharedPhotoLibrary()
imageManager.performChanges({ () -> Void in
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
if let properAsset = request.placeholderForCreatedAsset {
localIdentifier = properAsset.localIdentifier
}
else {
completion(asset: nil)
}
}, completionHandler: { (success, error) -> Void in
if let properLocalIdentifier = localIdentifier {
let result = PHAsset.fetchAssetsWithLocalIdentifiers([properLocalIdentifier], options: nil)
if result.count > 0 {
completion(asset: result[0] as? PHAsset)
}
else {
completion(asset: nil)
}
}
else {
completion(asset: nil)
}
})
}
I have tried this code, to save and get back the PHAsset. But the problem is that this PHAsset does not have any location associated with it, wondering why? And what I missed?
I believe that I don't have to manually set GPS data into image's metadata right? I think that Photos Framework or Asset Library takes care of it. So as you know that Asset Library is deprecated our only option is to use Photos Framework. I read online that, Saving image to Photo Library takes care of it. Isn't it correct?
Is there any alternative? Should I use UIImageWriteToSavedPhotosAlbum method to save image to Camera Roll, And I can take back the very recent photo using Photos Framework. But I don't think that UIImageWriteToSavedPhotosAlbum will take care of the location thing.
Do you have any thoughts?
First of all thanking all who were all kind to take a look into the question.
I found my answer.
//MARK: Saving an Image to PhotoLibrary and taking back the PHAsset
class func savingThis(image : UIImage, completion : (asset : PHAsset?) -> ())
{
var localIdentifier : String?
let imageManager = PHPhotoLibrary.sharedPhotoLibrary()
imageManager.performChanges({ () -> Void in
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
request.location = // Assigned current location here :)
if let properAsset = request.placeholderForCreatedAsset {
localIdentifier = properAsset.localIdentifier
}
else {
completion(asset: nil)
}
}, completionHandler: { (success, error) -> Void in
if let properLocalIdentifier = localIdentifier {
let result = PHAsset.fetchAssetsWithLocalIdentifiers([properLocalIdentifier], options: nil)
if result.count > 0 {
completion(asset: let asset = result[0] as? PHAsset)
}
else {
completion(asset: nil)
}
}
else {
completion(asset: nil)
}
})
}
When you get a callback from UIImagePickerController:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo
info: [UIImagePickerController.InfoKey : Any]) {}
you can try to get the image location from the info dictionary.
For that, you need to request the related PHAsset object:
if let assetImage = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
print("Image location info = \( assetImage.location))")
}
Important - before using this approach you need to request the user permission:
PHPhotoLibrary.requestAuthorization()
If you do not do it the info dictionary for UIImagePickerController.InfoKey.phAsset will return nil.
I am trying to convert UIImage into PHAsset but could not find any solution. All I found is the vice versa (i.e. PHAsset to UIImage).
The scenario is, I am fetching images from a custom directory into PHAssetCollection and showing in UICollectionView. I also want to include few more images here from previous screen which are coming from remote source. I don't want to persist remote images into my directory but want to include them in my UICollectionView.
Kindly suggest me solution or some good alternate so that the source of UICollectionView will be same (i.e. PHAssetCollection)
A bit late to the party, but I'll post my answer for people of the future.
What I did in my app was to take a screenshot and save to a dedicated album in the Photos app.
getAlbum() in the below example is just a helper method that retrieves the photo album.
ImageStoreItem is just a wrapper with two fields: image: UIImage and id: String for the identifier of the original PHAsset.
/**
Saves the image to the album in the Photos app.
- Throws:
- `ImageStore.TypeError.accessDenied`
if the user has denied access to the photo library.
- An error thrown by `PHPhotoLibrary`.
*/
func saveImage(_ image: UIImage) throws {
let album = try getAlbum()
try PHPhotoLibrary.shared().performChangesAndWait {
let imgReq = PHAssetChangeRequest.creationRequestForAsset(from: image),
albReq = PHAssetCollectionChangeRequest(for: album)!
albReq.addAssets([imgReq.placeholderForCreatedAsset] as NSFastEnumeration)
}
}
/**
Fetches images in the app album.
- Throws:
- `ImageStore.TypeError.accessDenied`
if the user has denied access to the photo library.
- An error thrown by `PHPhotoLibrary`.
- `NSError` thrown by `PHImageManager`
if fetching images from the `PHAsset` list failed.
- `Globalerror.unknown` if no image was fetched,
but there is no corresponding error object.
- Returns:
An array of `ImageStoreItem`s with the album items.
*/
func getImages() throws -> [ImageStoreItem] {
let album = try ImageStore.shared.getAlbum(),
assets = PHAsset.fetchAssets(in: album, options: nil)
let size = UIScreen.main.bounds.size,
opt = PHImageRequestOptions()
opt.isSynchronous = true
var res = [ImageStoreItem]()
var err: Error? = nil
let handler: (PHAsset) -> (UIImage?, [AnyHashable : Any]?) -> Void =
{ (asset) in
{ (image, info) in
if let image = image {
let item = ImageStoreItem(image: image,
id: asset.localIdentifier)
res.append(item)
} else if let info = info {
if let error = info[PHImageErrorKey] {
err = error as! NSError
} else {
var userInfo = [String : Any]()
for (key, value) in info {
userInfo[String(describing: key)] = value
}
err = GlobalError.unknown(info: userInfo)
}
}
}
}
assets.enumerateObjects { (asset, _, _) in
PHImageManager.default()
.requestImage(for: asset,
targetSize: size,
contentMode: .default,
options: opt,
resultHandler: handler(asset))
}
if let error = err { throw error }
return res
}
The documentation on the PhotoKit isn't too impressive, but take a look here and there and you'll get the hang of it.
You do not want to convert a PHAsset to UIImage. I do not know if it is possible; it shouldn't be, since PHAsset and UIImage have different properties, behaviours, etc. The vice versa is possible because all the properties of UIImage can be derived from PHAsset but not vice-versa.
Instead, change the data source of the collection view to [UIImage] while converting PHAsset to UIImage instead. This approach is clean and solves your other problem of having to convert UIImage to PHImage.
Now that AssetsLibrary has been deprecated, we're supposed to use the photos framework, specifically PHPhotoLibrary to save images and videos to a users camera roll.
Using ReactiveCocoa, such a request would look like:
func saveImageAsAsset(url: NSURL) -> SignalProducer<String, NSError> {
return SignalProducer { observer, disposable in
var imageIdentifier: String?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let changeRequest = PHAssetChangeRequest.creationRequestForAssetFromImageAtFileURL(url)
let placeholder = changeRequest?.placeholderForCreatedAsset
imageIdentifier = placeholder?.localIdentifier
}, completionHandler: { success, error in
if let identifier = imageIdentifier where success {
observer.sendNext(identifier)
} else if let error = error {
observer.sendFailed(error)
return
}
observer.sendCompleted()
})
}
}
I created a gif from a video using Regift and I can verify that the gif exists inside my temporary directory. However when I go save that gif to the camera roll, I get a mysterious error: NSCocoaErrorDomain -1 (null), which is really super helpful.
Has anyone ever experienced this issue?
You can try this.
let data = try? Data(contentsOf: /*Your-File-URL-Path*/)
PHPhotoLibrary.shared().performChanges({
PHAssetCreationRequest.forAsset().addResource(with: .photo, data: data!, options: nil)
})
I'm trying to do the equivalent of writeImageToSavedPhotosAlbum with the new Photo framework.
To save the image, I only do this:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as UIImage
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let changeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
}, completionHandler: { (success, error) -> Void in
//
})
}
Of course, there's no magic and since I don't do anything of
info[UIImagePickerControllerMediaMetadata]
, the above code doesn't save any metadata to the "Camera Roll", as you can see using the screenshot of the Preview.app when I connect my iPhone to my Mac.
You get that view by opening Preview.app, selecting File > Import from "you device name"; then you can browse your pictures and see that those taken with the Camera app show exif data such as focal length, while those saved with the above code show empty values.
Now the documentation for creationRequestForAssetFromImage says:
To set metadata properties of the newly created asset, use the
corresponding properties of the change request (listed in Modifying
Assets).
Which links to "+changeRequestForAsset:" and 4 properties (creationDate, location, favorite, hidden), that's a little light. What about the other properties one might want to save (like aperture, focal length, shutter speed, …)?
How are you supposed to save your meta data along the image with the Photo framework?
Here's what I ended up doing:
extension UIImage {
/**
Gets the metadata from the photo album
:param: info The picker dictionary
:param: completionHandler A block to call when the metadata is available
*/
class func requestMetadata(info: [NSObject : AnyObject], completionHandler: ([NSObject : AnyObject]) -> Void) {
let assetUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let result = PHAsset.fetchAssetsWithALAssetURLs([assetUrl], options: nil)
if let asset = result.firstObject as? PHAsset {
let editOptions = PHContentEditingInputRequestOptions()
editOptions.networkAccessAllowed = true
asset.requestContentEditingInputWithOptions(editOptions, completionHandler: { (contentEditingInput, _) -> Void in
let url = contentEditingInput.fullSizeImageURL
let orientation = contentEditingInput.fullSizeImageOrientation
var inputImage = CoreImage.CIImage(contentsOfURL: url)
completionHandler(inputImage.properties())
})
} else {
completionHandler([:])
}
}
}