Getting the URL of picture taken by camera with Photos Framework - ios

I have an app that uses a UIImagePickerController to retrieve pictures both from camera and from the photos library.
In the image picker delegate I only want to save the NSURL (UIImagePickerControllerReferenceURL) of the picked image to save memory. When the user needs to see the image later on, I load it with PHCachingImageManager directly from the photos library.
Now - this whole thing works great with pictures the user chooses from the library, but not with pictures directly taken by camera (since there is no URL). I am currently trying to save the picture with PHAsset, but I have no idea how to get the NSURL of the save picture.
This is what I've been up to:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])
{
picker.dismissViewControllerAnimated(true, completion: nil)
let pickedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
if picker.sourceType == .Camera
{
// When taking a picture with the camera, store it in the user roll
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
{ () -> Void in
// save the image
PHAssetCreationRequest.creationRequestForAssetFromImage(pickedImage)
// TODO how to get the asset url
}, completionHandler:
{ (finished, error) -> Void in
if (finished)
{
}
}
)
}
else
{
let pickedImageUrl: NSURL? = info[UIImagePickerControllerReferenceURL] as? NSURL
currentImageUrl = pickedImageUrl
currentImage = pickedImage
toggleImageInfoView(true)
toggleMapState(true)
}
}
Any ideas how to get the url of the saved picture?
Best,
Georg

UPDATE: Seems like I found an answer to this Problem.
Step 1: I save the image to the camera
UIImageWriteToSavedPhotosAlbum(image.image, self, #selector(cameraImageSavedAsynchronously), nil)
this is done asynchronously, so make sure to set a selector when operation has finished.
Step 2: When operation has completed, I do the following:
func fetchLastImage(completion: (localIdentifier: String?) -> Void)
{
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: fetchOptions)
if (fetchResult.firstObject != nil)
{
let lastImageAsset: PHAsset = fetchResult.firstObject as! PHAsset
completion(localIdentifier: lastImageAsset.localIdentifier)
}
else
{
completion(localIdentifier: nil)
}
}
I fetch the last image in camera roll with PHAsset and save the local identifier of the image. This is not an URL, but a unique identifier which does not change. This way, you can access the saved image perfectly.
Hope this helps others!

I agree with you.
but, if the Image's Exif has the date of the earlier .
let fetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: fetchOptions)
fetchResult.firstObject
fetchResult.firstObject is not the one you just saved.
maybe you can modify fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false) to key: "modificationDate"
BTW, I found an other way:
__block PHObjectPlaceholder *placeholderAsset = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *newAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
newAssetRequest.location = location;
newAssetRequest.creationDate = [NSDate date];
placeholderAsset = newAssetRequest.placeholderForCreatedAsset;
} completionHandler:^(BOOL success, NSError *error) {
if(success){
PHAsset *asset = [self getAssetFromlocalIdentifier:placeholderAsset.localIdentifier];
completionBlock(asset, YES);
} else {
completionBlock(nil, NO);
}
}];
can get the newly PHAsset.

Ive updated the answer to include returning any asset type, as well as simpler/cleaner way of returning the asset.
Theres no need to a competition handler.
func fetchLastAsset() -> PHAsset? {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult = PHAsset.fetchAssets(with: fetchOptions)
return fetchResult.firstObject
}

Related

How to retrieve PHAsset from UIImagePickerController

I'm trying to retrieve a PHAsset however PHAsset.fetchAssets(withALAssetURLs:options:) is deprecated from iOS 8 so how can I properly retrieve a PHAsset?
I had the same the issue, first check permissions and request access:
let status = PHPhotoLibrary.authorizationStatus()
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
})
}
Just hook that up to whatever triggers your UIImagePickerController. The delegate call should now include the PHAsset in the userInfo.
guard let asset = info[UIImagePickerControllerPHAsset] as? PHAsset
Here is my solution:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if #available(iOS 11.0, *) {
let asset = info[UIImagePickerControllerPHAsset]
} else {
if let assetURL = info[UIImagePickerControllerReferenceURL] as? URL {
let result = PHAsset.fetchAssets(withALAssetURLs: [assetURL], options: nil)
let asset = result.firstObject
}
}
}
The PHAsset will not appear in the didFinishPickingMediaWithInfo: info result unless the user has authorized, which did not happen for me just by presenting the picker. I added this in the Coordinator init():
let status = PHPhotoLibrary.authorizationStatus()
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
})
}
I am not sure what you want.
Are you trying to target iOS 8?
This is how I fetch photos and it works in iOS (8.0 and later), macOS (10.11 and later), tvOS (10.0 and later).
Code is commented where it may be confusing
The first functions sets the options to fetch the photos
The second function will actually fetch them
//import the Photos framework
import Photos
//in these arrays I store my images and assets
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func setPhotoOptions() -> PHFetchOptions{
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 15
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: setPhotoOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects({ (asset, count, stop) in
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
//basically, here you can do what you want
//(after you finish retrieving your assets)
//I am reloading my collection view
self.collectionView?.reloadData()
}
}
})
})
}
}
Edit based on OP's clarification
You need to set the delegate UIImagePickerControllerDelegate
then implement the following function
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
within said method, get the image like this:
var image : UIImage = info[UIImagePickerControllerEditedImage] as! UIImage

Save video to the gallery and get the path for the video stored to Gallery

I want to save a video chosen from UIImagePickerController to the Photos album.
After that i need to fetch the URL to the saved path. I saw some pre-existing questions which answer with use of ALAssetsLibrary which is now deprecated.
Like : Save video on the gallery and store the path to the video
Can someone please guide me to use Photo's framework and achieve the above desired result. Kindly correct me if I am wrong anywhere.
Thanks!!
This worked for me perfectly.
Swift 3.1 ->
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
// After uploading we fetch the PHAsset for most recent video and then get its current location url
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).lastObject
PHImageManager().requestAVAsset(forVideo: fetchResult!, options: nil, resultHandler: { (avurlAsset, audioMix, dict) in
let newObj = avurlAsset as! AVURLAsset
print(newObj.url)
// This is the URL we need now to access the video from gallery directly.
})
}
}
First, you need to set the following permission in your app's plist file:
Privacy - Photo Library Usage Description
Provide a string that is presented to the user explaining why you are requesting the permission.
Next,
import photos
Then use this code to store video
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileURL)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
// fetchResult is your latest video PHAsset
// To fetch latest image replace .video with .image
}
}
To get url from PHAsset, refer to this question's answer
If you want to fetch the saved element (not using a sort descriptor with date order), you can do it this way:
var changeRequest: PHAssetChangeRequest?
var blockPlaceholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
changeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
blockPlaceholder = changeRequest?.placeholderForCreatedAsset
}) { saved, error in
if saved {
guard let placeholder = blockPlaceholder else {
return
}
let fetchOptions = PHFetchOptions()
let fetchResult:PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: fetchOptions)
if let asset = fetchResult.firstObject {
//here you have the PHAsset
}
}
}
Try like this!
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){
if mediaType.isEqualToString(kUTTypeMovie as NSString as String) || mediaType.isEqualToString(kUTTypeVideo as NSString as String){
let videoPath = info[UIImagePickerControllerMediaURL] as! NSURL // Path of video Url
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(videoPath)
let assetPlaceholder = createAssetRequest?.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
albumChangeRequest!.addAssets([assetPlaceholder!])
}, completionHandler: { (success, error) in
NSLog("Adding video to Library ->%#", (success ? "Success" : "Error"))
picker.dismissViewControllerAnimated(true, completion: nil)
})
}
}
Here's the answer for Objective-C
Code Sample:
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL URLWithString:urlOfFile]];
} completionHandler:^(BOOL success, NSError *error) {
if (success)
{
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc]init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:false]];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:fetchOptions];
PHAsset *lastAsset = [fetchResult lastObject];
[[PHImageManager defaultManager] requestAVAssetForVideo:lastAsset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
//here's the url to the file
NSURL *url = (NSURL *)[(AVURLAsset *)asset URL];
}else{
NSLog(#"%#",error.description);
}
}];
I do this by creating extension of the URL Class
extension URL {
func saveVideo( success:#escaping (Bool,URL?)->()){
URLSession.shared.downloadTask(with: URLRequest(url: self)) { (url, response, error) in
let mgr = FileManager.default
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let destination = URL(fileURLWithPath: String(format: "%#/%#", documentsPath, "video.mp4"))
try? mgr.moveItem(atPath: /url?.path, toPath: /destination.path)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: destination)
}) { completed, error in
if completed {
print("Video is saved!")
success(true,destination)
}
if error != nil{
success(false,nil)
}
}
}.resume()
}
}
when you want to save and play you can use this in ViewController as
guard let urlToDownload = URL(string: videoURLStringToDownload) else { return }
urlToDownload.saveVideo(success: { (isSaved,url) in
guard let videoURL = url, isSaved else { return }
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player?.play()
}
})
import AVKit for playing videos through AVPlayerViewController
import Photos and PhotosUI for saving Video with PHAsset.
Hope it helps!

iOS - Fetch all photos from device and save them to app

I would like to fetch all photos that are saved in device and save them to my app and then eventually (if user allow this) delete originals.
This is my whole class I created for this task:
class ImageAssetsManager: NSObject {
let imageManager = PHCachingImageManager()
func fetchAllImages() {
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.Image.rawValue)
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
if #available(iOS 9.0, *) {
options.fetchLimit = 5
} else {
// Fallback on earlier versions
}
let imageAssets = PHAsset.fetchAssetsWithOptions(options)
print(imageAssets.count)
self.getAssets(imageAssets)
}
func getAssets(assets: PHFetchResult) {
var assetsToDelete: [PHAsset] = []
assets.enumerateObjectsUsingBlock { (object, count, stop) in
if object is PHAsset {
let asset = object as! PHAsset
let imageSize = CGSize(width: asset.pixelWidth,height: asset.pixelHeight)
let options = PHImageRequestOptions()
options.deliveryMode = .FastFormat
options.synchronous = true
self.imageManager.requestImageForAsset(asset, targetSize: imageSize, contentMode: .AspectFill, options: options, resultHandler: { [weak self]
image, info in
self.addAssetToSync(image, info: info)
assetsToDelete.append(asset)
})
}
}
self.deleteAssets(assetsToDelete)
}
func addAssetToSync(image: UIImage?, info: [NSObject : AnyObject]?) {
guard let image = image else {
return
}
guard let info = info else {
return
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let imageData = UIImageJPEGRepresentation(image, 0.95)!
let fileUrl = info["PHImageFileURLKey"] as! NSURL
dispatch_async(dispatch_get_main_queue(), {
let photoRootItem = DatabaseManager.sharedInstance.getPhotosRootItem()
let ssid = DatabaseManager.sharedInstance.getSsidInfoByName(ContentManager.sharedInstance.ssid)
let item = StorageManager.sharedInstance.createFile(imageData, name: fileUrl.absoluteString.fileNameWithoutPath(), parentFolder: photoRootItem!, ssid: ssid!)
})
})
}
func deleteAssets(assetsToDelete: [PHAsset]){
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(assetsToDelete)
}, completionHandler: { success, error in
guard let error = error else {return}
})
}
}
It's working but my problem is that it's working just for a limited number of photos. When I try it with all I get memory warnings and then app crashed. I know why it is. I know that my problem is that I get all photos to memory and it's too much. I could fetch images with that fetch limit and make it to loop but I am not sure if it is best solution.
I was hoping that with some solution process few photos then release memory and again and again until end. But this change would be somewhere in enumerateObjectsUsingBlock. I am not sure if it helps but I don't even need to get image. I just need to copy image file from device path to my app sandbox path.
What's best solution for this? How to avoid memory warnings and leaks? Thanks
Change your dispatch_async calls to dispatch_sync. Then you will process photos one at a time as you walk through enumerateObjectsUsingBlock, instead of trying to process them all at the same time.

how to get UIImage metadata in swift

I'm using UIImagePickerController to take photos with camera and also to get photos from SavedPhotosAlbum library. Once user takes a photo I save it in SavedPhotosAlbum and the following method is called:
override func image(image: UIImage, didFinishSavingWithError: NSErrorPointer, contextInfo:UnsafePointer<Void>) {
if (didFinishSavingWithError != nil) {
print("Error saving photo: \(didFinishSavingWithError)")
} else {
let photoToSend = CompressAndSendPhoto(image: image)
photoToSend.uploadImageRequest()
print("Successfully saved photo, will make request to update asset metadata")
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)
let lastImageAsset = fetchResult.lastObject as! PHAsset
let coordinate = CLLocationCoordinate2DMake(self.coordinate1, self.coordinate2)
let nowDate = NSDate()
let myLocation = CLLocation(coordinate: coordinate, altitude: 0.0, horizontalAccuracy: 1.0, verticalAccuracy: 1.0, timestamp: nowDate)
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetChangeRequest = PHAssetChangeRequest(forAsset: lastImageAsset)
assetChangeRequest.location = myLocation
}, completionHandler: {
(success:Bool, error:NSError?) -> Void in
if (success) {
print("Succesfully saved metadata to asset")
print("location metadata = \(myLocation)")
} else {
print("Failed to save metadata to asset with error: \(error!)")
}
});
}
}
and it works fine, user current location is being added to the photo asset.
The problem is that I can not get this value while choosing a photo from SavedPhotosAlbum. I googled many options but none of them works. How can I do it in method below?
func imagePickerController(
picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
if picker.sourceType == UIImagePickerControllerSourceType.SavedPhotosAlbum {
}
dismissViewControllerAnimated(true, completion: nil)
}
Also I would like to add more "fields" to photo asset, not only location which is one of default ones, how can I add custom NSDictionary of values?

How to delete the last photo in Swift iOS8?

I try to get the last photo from camera roll and delete it.Now I get the last photo but have problems in deleting the last photo.
I tried this way but I delete all photos,so I plan to build a new PHFetchResult which only include the last photo but I don't know how to do that.
PHPhotoLibrary.sharedPhotoLibrary().performChanges( {
PHAssetChangeRequest.deleteAssets(fetchResult)},
completionHandler: {
success, error in
NSLog("Finished deleting asset. %#", (success ? "Success" : error))
})
Thank all of you to answer my question!
I guess the problem lies in your fetchResult.
You will have to pass an array that contains only latest image from photo library.
Try making that array as follows-
var fetchOptions: PHFetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
var fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)
if (fetchResult.lastObject != nil) {
var lastAsset: PHAsset = fetchResult.lastObject as PHAsset
let arrayToDelete = NSArray(object: lastAsset)
PHPhotoLibrary.sharedPhotoLibrary().performChanges( {
PHAssetChangeRequest.deleteAssets(arrayToDelete)},
completionHandler: {
success, error in
NSLog("Finished deleting asset. %#", (success ? "Success" : error))
})
}
See this link.

Resources