38

I am making an app that takes pictures with AVFoundation and I want to save them to a custom album that I can then query and show in my app. (I'd prefer to not have them in the general photo roll, unless the user wants that) I can't really find anything showing how to do this in Swift... or at all. Is there a different way I am supposed to do this?

I found this example on SO but it doesn't make sense to me and I can't get it to work.

    func savePhoto() {
    var albumFound : Bool = false
    var assetCollection: PHAssetCollection!
    var photosAsset: PHFetchResult!
    var assetThumbnailSize:CGSize!

    // Create the album if does not exist (in viewDidLoad)
    if let first_Obj:AnyObject = collection.firstObject{
        //found the album
        self.albumFound = true
        self.assetCollection = collection.firstObject as PHAssetCollection
    }else{
        //Album placeholder for the asset collection, used to reference collection in completion handler
        var albumPlaceholder:PHObjectPlaceholder!
        //create the folder
        NSLog("\nFolder \"%@\" does not exist\nCreating now...", albumName)
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            let request = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(albumName)
            albumPlaceholder = request.placeholderForCreatedAssetCollection
            },
            completionHandler: {(success:Bool, error:NSError!)in
                NSLog("Creation of folder -> %@", (success ? "Success":"Error!"))
                self.albumFound = (success ? true:false)
                if(success){
                    let collection = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([albumPlaceholder.localIdentifier], options: nil)
                    self.assetCollection = collection?.firstObject as PHAssetCollection
                }
        })
    }



    let bundle = NSBundle.mainBundle()
    let myFilePath = bundle.pathForResource("highlight1", ofType: "mov")
    let videoURL:NSURL = NSURL.fileURLWithPath(myFilePath!)!

    let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
    dispatch_async(dispatch_get_global_queue(priority, 0), {
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            //let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage
            let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(videoURL)
            let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset
            let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
            albumChangeRequest.addAssets([assetPlaceholder])
            }, completionHandler: {(success, error)in
                dispatch_async(dispatch_get_main_queue(), {
                    NSLog("Adding Image to Library -> %@", (success ? "Sucess":"Error!"))
                    //picker.dismissViewControllerAnimated(true, completion: nil)
                })
        })

    })
}

Any help/explanations would be great!

Jonovono
  • 2,517
  • 6
  • 26
  • 49

5 Answers5

42

This is how I do:

At the top:

import Photos

var image: UIImage!
var assetCollection: PHAssetCollection!
var albumFound : Bool = false
var photosAsset: PHFetchResult!
var assetThumbnailSize:CGSize!
var collection: PHAssetCollection!
var assetCollectionPlaceholder: PHObjectPlaceholder!

Creating the album:

func createAlbum() {
    //Get PHFetch Options
    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", "camcam")
    let collection : PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
    //Check return value - If found, then get the first album out
    if let _: AnyObject = collection.firstObject {
        self.albumFound = true
        assetCollection = collection.firstObject as! PHAssetCollection
    } else {
        //If not found - Then create a new album
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle("camcam")
            self.assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
            }, completionHandler: { success, error in
                self.albumFound = success

                if (success) {
                    let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([self.assetCollectionPlaceholder.localIdentifier], options: nil)
                    print(collectionFetchResult)
                    self.assetCollection = collectionFetchResult.firstObject as! PHAssetCollection
                }
        })
    }
}

When saving the photo:

func saveImage(){
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({
            let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(self.image)
            let assetPlaceholder = assetRequest.placeholderForCreatedAsset
            let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
            albumChangeRequest!.addAssets([assetPlaceholder!])
            }, completionHandler: { success, error in
                print("added image to album")
                print(error)

                self.showImages()
        })
    }

Showing the images from that album:

func showImages() {
        //This will fetch all the assets in the collection

        let assets : PHFetchResult = PHAsset.fetchAssetsInAssetCollection(assetCollection, options: nil)
        print(assets)

        let imageManager = PHCachingImageManager()
        //Enumerating objects to get a chached image - This is to save loading time
        assets.enumerateObjectsUsingBlock{(object: AnyObject!,
            count: Int,
            stop: UnsafeMutablePointer<ObjCBool>) in

            if object is PHAsset {
                let asset = object as! PHAsset
                print(asset)

                let imageSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)

                let options = PHImageRequestOptions()
                options.deliveryMode = .FastFormat

                imageManager.requestImageForAsset(asset, targetSize: imageSize, contentMode: .AspectFill, options: options, resultHandler: {(image: UIImage?,
                    info: [NSObject : AnyObject]?) in
                    print(info)
                    print(image)
                })
            }
        }
Burhanuddin Sunelwala
  • 4,994
  • 2
  • 22
  • 48
Jonovono
  • 2,517
  • 6
  • 26
  • 49
  • Looks like you are missing code in the section "When saving the photo" – djunod Feb 10 '15 at 04:19
  • 1
    Thank for your answer, but your code has lots of typos and there's a bunch missing. Do you think you could please tidy up your answers? That would be really helpful – ndbroadbent Mar 13 '15 at 21:30
  • Can someone please provide objective-c that is the **exact equivalent** of this code? – Adam Freeman Jul 21 '15 at 22:02
  • 1
    This worked for me, but I have one more thing to implement. How can I get the URL/Path of an image, so that I can store it in my data model. – Ankita Shah Oct 01 '15 at 05:54
  • 1
    how to save this image with a custom filename ?? – Jirson Tavera Sep 09 '16 at 17:52
  • Can you please provide swift 3 variant ? It gives error : Contextual type 'NSFastEnumeration' cannot be used with array literal. at line : albumChangeRequest!.addAssets([assetPlaceholder!]) – Priyal Jan 09 '17 at 15:40
  • solved error! But I am not able to add an image to album after application restarts. Any help ? I have requirement similar to Instagram. I want to create an album named - "my album" and to add all those photos to this album which user has uploaded through my app. – Priyal Jan 10 '17 at 11:07
29

Answer in Objective-C and cleaned up a bit.

__block PHFetchResult *photosAsset;
__block PHAssetCollection *collection;
__block PHObjectPlaceholder *placeholder;

// Find the album
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.predicate = [NSPredicate predicateWithFormat:@"title = %@", @"YOUR_ALBUM_TITLE"];
collection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum
                                                      subtype:PHAssetCollectionSubtypeAny
                                                      options:fetchOptions].firstObject;
// Create the album
if (!collection)
{
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetCollectionChangeRequest *createAlbum = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:@"YOUR_ALBUM_TITLE"];
        placeholder = [createAlbum placeholderForCreatedAssetCollection];
    } completionHandler:^(BOOL success, NSError *error) {
        if (success)
        {
            PHFetchResult *collectionFetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[placeholder.localIdentifier]
                                                                                                            options:nil];
            collection = collectionFetchResult.firstObject;
        }
    }];
}

// Save to the album
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
    PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:[UIImage imageWithData:imageData]];
    placeholder = [assetRequest placeholderForCreatedAsset];
    photosAsset = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
    PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:collection
                                                                                                                  assets:photosAsset];
    [albumChangeRequest addAssets:@[placeholder]];
} completionHandler:^(BOOL success, NSError *error) {
    if (success)
    {
        NSString *UUID = [placeholder.localIdentifier substringToIndex:36];
        self.photo.assetURL = [NSString stringWithFormat:@"assets-library://asset/asset.PNG?id=%@&ext=JPG", UUID];
        [self savePhoto];
    }
    else
    {
        NSLog(@"%@", error);
    }
}];

The bit at the end with the UUID was something I found on another StackOverflow thread for creating a replacement for AssetURL property from an ALAsset.

Note: See chris' comment below for more complete answer.

Community
  • 1
  • 1
cschandler
  • 554
  • 5
  • 15
  • @cschandler > what does [self savePhoto]; does? – Rashad Nov 10 '15 at 04:40
  • 4
    Is there an issue with this approach having two asynchronous requests (i.e., the `performChanges` calls) where one depends on the result of the other? Won't this introduce a race condition? – chris Dec 02 '15 at 07:45
  • You're probably right about that. I guess I would use a dispatch group to make sure the call finishes, or just move the 'Save to the album' section inside the completion handler for 'Create the album' section for full safety. – cschandler Dec 29 '15 at 16:10
7

I like to reuse the code I write so I decided to create an extension for PHPhotoLibrary where it is possible to use it like:

PHPhotoLibrary.saveImage(photo, albumName: "Trip") { asset in
    guard let asset = asset else {
        assert(false, "Asset is nil")
        return
    }
    PHPhotoLibrary.loadThumbnailFromAsset(asset) { thumbnail in
        print(thumbnail)
    }
}

Here is the code: https://gist.github.com/ricardopereira/636ccd0a3c8a327c43d42e7cbca4d041

ricardopereira
  • 9,557
  • 4
  • 53
  • 68
  • 2
    Thanks so much! I improved on it a little bit, added video, as well as a category which let me compile it ( PHAsset+identifier.swift) https://github.com/kv2/PHPhotoLibrary-PhotoAsset.swift – stackOverFlew Aug 03 '16 at 21:15
  • 1
    Type 'PHAsset' has no member 'fetchAssetsWithLocalIdentifier'. It does have fetchAssetsWithLocalIndentifiers but it has a different parameter list that i dont know how to edit – Xitcod13 Aug 21 '16 at 23:22
  • 1
    Alright got it working. Needed to replace fetchAssetWithLocalIdentifier with PHAsset.fetchAssetsWithLocalIdentifiers([placeholder.localIdentifier], options: nil).firstObject as? PHAsset – Xitcod13 Aug 22 '16 at 01:14
  • How would I proceed to get the path of the image to store it in the db?? – Xitcod13 Aug 22 '16 at 01:34
  • @Xitcod13 You don't need the path. You just need to save the [`localIdentifier`](https://developer.apple.com/library/ios/documentation/Photos/Reference/PHObject_Class/index.html#//apple_ref/occ/instp/PHObject/localIdentifier) (_A unique string that persistently identifies the object._). – ricardopereira Aug 22 '16 at 06:33
  • Any chance you could write that in Objective-C? – user-44651 Jun 08 '17 at 17:27
2

As updated for Swift 2.1+ and for Video in case you are trying to do that and ended up here. Compare to the other answers for slight differences (such as using for Images rather than Video)

var photosAsset: PHFetchResult!
var collection: PHAssetCollection!
var assetCollectionPlaceholder: PHObjectPlaceholder!

//Make sure we have custom album for this app if haven't already
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", "MY_APP_ALBUM_NAME")
self.collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions).firstObject as! PHAssetCollection

//if we don't have a special album for this app yet then make one
if self.collection == nil {
     PHPhotoLibrary.sharedPhotoLibrary().performChanges({
        let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle("MY_APP_ALBUM_NAME")
        self.assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
             }, completionHandler: { success, error in
                if success {
                    let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([self.assetCollectionPlaceholder.localIdentifier], options: nil)
                    print(collectionFetchResult)
                    self.collection = collectionFetchResult.firstObject as! PHAssetCollection
                }
            })
 }

//save the video to Photos                     
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
     let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(self.VIDEO_URL_FOR_VIDEO_YOU_MADE!)
     let assetPlaceholder = assetRequest!.placeholderForCreatedAsset
     self.photosAsset = PHAsset.fetchAssetsInAssetCollection(self.collection, options: nil)
     let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.collection, assets: self.photosAsset)
     albumChangeRequest!.addAssets([assetPlaceholder!])
         }, completionHandler: { success, error in
               if success {
                  print("added video to album")
               }else if error != nil{
                  print("handle error since couldn't save video")
               }
         }
 })
ColossalChris
  • 5,078
  • 2
  • 34
  • 43
0

I improved on @ricardopereira and @ColossalChris code. Added video to the extension, and added another extension on top of PHAsset to get rid of the compilation errors. Works in Swift 2.1.

Sample usage:

#import "Yourtargetname-Swift.h"//important!
NSURL *videoURL = [[NSURL alloc] initFileURLWithPath:PATH_TO_VIDEO];

[PHPhotoLibrary saveVideo:videoURL albumName:@"my album" completion:^(PHAsset * asset) {
    NSLog(@"success");
    NSLog(@"asset%lu",(unsigned long)asset.pixelWidth);
}];

Import both swift files: https://github.com/kv2/PHPhotoLibrary-PhotoAsset.swift

It is usable in objective-c as long as you import the swift header for your target (see the ViewController.m file).

fatihyildizhan
  • 7,103
  • 5
  • 54
  • 74
stackOverFlew
  • 1,449
  • 1
  • 30
  • 56