24
votes

I'm trying to create a custom album in the Photo Library of an iPhone and then save photos that I've taken with the camera, or chosen from the phones Camera Roll to that custom album. I can successfully create the album but the photos are not getting saved there, instead they are getting saved to the simulators Saved Photos album... I'm not sure how to tell UIImageWriteToSavedPhotosAlbum to save to the new album I've just created using addAssetsGroupAlbumWithName...

Here is the code I have so far - I've snipped out a few sections to keep my code example short...

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{     
  NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
  if([mediaType isEqualToString:(__bridge NSString *)kUTTypeImage])
  {        
    // pull GPS information from photos metadata using ALAssetsLibrary
    void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *) = ^(ALAsset *asset)
    {
        // code snipped out 
    };
    NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library assetForURL:assetURL
             resultBlock:ALAssetsLibraryAssetForURLResultBlock
            failureBlock:^(NSError *error) 
            {
                // code snipped out
            }];

    // getimage from imagePicker and resize it to the max size of the iPhone screen 
    UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage]; 
    UIImage *resizedImage = [util_ createThumbnailForImage:originalImage thumbnailSize:[util_ determineIPhoneScreenSize]];
    NSData *imageData = UIImagePNGRepresentation(resizedImage);

                // code snipped out
                // code snipped out
                // code snipped out
                // code snipped out
                // code snipped out
                // code snipped out



    // create a new album called "My Apps Photos"
    [library addAssetsGroupAlbumWithName:@"My Apps Photos"
            resultBlock:^(ALAssetsGroup *group) 
            {
                NSLog(@"in addAssetsGroupAlbumWithName resultBlock");

                // save file to album
                UIImageWriteToSavedPhotosAlbum(resizedImage, self, nil, nil);

            } 
            failureBlock:^(NSError *error) 
            {
                NSLog(@"in addAssetsGroupAlbumWithName failureBlock");

            }
     ];
  }
}

So... Like I said, it creates the new album but does not save the photo there. How do I tell it to save into the new album? Perhaps I sound not use UIImageWriteToSavedPhotosAlbum??

Note: I'm using Xcode 4.3.2, IOS 5.1, and ARC

5

5 Answers

60
votes

If you are using iOS6, Fernando's answer will not work, because the saveImage selector is no longer available.

The process is pretty confusing, and I have not seen any clear answers posted, so here is the method I've used to solve this in iOS6.

You will need to use a combination of the following:

Create the Album:

[self.library addAssetsGroupAlbumWithName:albumName 
                              resultBlock:^(ALAssetsGroup *group) {
         NSLog(@"added album:%@", albumName);
}
                             failureBlock:^(NSError *error) {
         NSLog(@"error adding album");
}];

Find the Album:

__block ALAssetsGroup* groupToAddTo;
[self.library enumerateGroupsWithTypes:ALAssetsGroupAlbum
                             usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
      if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:albumName]) {
          NSLog(@"found album %@", albumName);
          groupToAddTo = group;
      }
}
                           failureBlock:^(NSError* error) {
     NSLog(@"failed to enumerate albums:\nError: %@", [error localizedDescription]);
}];

Save the Image to Asset Library, and put it into the album:

CGImageRef img = [image CGImage];
[self.library writeImageToSavedPhotosAlbum:img
                                  metadata:[info objectForKey:UIImagePickerControllerMediaMetadata]
                           completionBlock:^(NSURL* assetURL, NSError* error) {
     if (error.code == 0) {
         NSLog(@"saved image completed:\nurl: %@", assetURL);

         // try to get the asset
         [self.library assetForURL:assetURL
                       resultBlock:^(ALAsset *asset) {
              // assign the photo to the album
              [groupToAddTo addAsset:asset];
              NSLog(@"Added %@ to %@", [[asset defaultRepresentation] filename], albumName);
          }
                      failureBlock:^(NSError* error) {
              NSLog(@"failed to retrieve image asset:\nError: %@ ", [error localizedDescription]);
          }];
     }
     else {
         NSLog(@"saved image failed.\nerror code %i\n%@", error.code, [error localizedDescription]);
     }
 }];
12
votes

Fernando's answer worked for me in iOS 7.

Steps :

1) Download the ALAssetsLibrary+CustomPhotoAlbum code from here: http://www.touch-code-magazine.com/wp-content/uploads/2011/11/ALAssetsLibrary_CustomPhotoAlbum.zip?a071b6 and copy the 2 files for the category inside your Xcode project.

2)In your header file,add the following lines

#import <AssetsLibrary/AssetsLibrary.h>
#import "ALAssetsLibrary+CustomPhotoAlbum.h"

@property (strong, atomic) ALAssetsLibrary* library;

3) In your implementation file,add the following lines

@synthesize library=_library;

EDIT: 4) initialise the asset library instance, preferably in "viewDidLoad" method, otherwise the saveImage method below won't execute. (CONFIRMED):

[I am resubmitting the suggested edit because someone with non-iphone skills rejected the previous submission. This is undoubtedly a great answer by @santhu & Fernando and helped me a lot, however, the initialisation piece of code was missing so it took me a bit of time to figure out why the code didn't work. Hence, I would appreciate if a moderator with iPhone development skillset reviews the edit.]

_library = [[ALAssetsLibrary alloc] init];

5) Add this in the method where you wish to save

  //Add this in the method where you wish to save
  [self.library saveImage:(UIImage *) toAlbum:(NSString *) withCompletionBlock:^(NSError                 *error) {
    if (error!=nil) {
        NSLog(@"Big error: %@", [error description]);
    }
}];
9
votes

For swift users:- I made function to do the same thing.

declare closure outsize class definition (above class definition)

typealias CompletionHandler = (success:Bool!) -> Void  

declare library variable inside class

var library:ALAssetsLibrary?;

initialize variable in viewDidLoad

library = ALAssetsLibrary();

method to add Image to Particular Album

func addImage(image:UIImage, metaData:NSDictionary, toAlbum albumName:String, handler:CompletionHandler){

    library?.addAssetsGroupAlbumWithName(albumName, resultBlock: {(group:ALAssetsGroup!) -> Void in
        print("\nAlbum Created:=  \(albumName)");
        /*-- Find Group --*/

        var groupToAddTo:ALAssetsGroup?;

        self.library?.enumerateGroupsWithTypes(ALAssetsGroupType(ALAssetsGroupAlbum),
            usingBlock: { (group:ALAssetsGroup?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in

                if(group != nil){

                    if group!.valueForProperty(ALAssetsGroupPropertyName) as String == albumName{
                        groupToAddTo = group;

                        print("\nGroup Found \(group!.valueForProperty(ALAssetsGroupPropertyName))\n");

                        self.library?.writeImageToSavedPhotosAlbum(image.CGImage, metadata:metaData, completionBlock: {(assetURL:NSURL!,error:NSError!) -> Void in

                            if(error == nil){
                                self.library?.assetForURL(assetURL,
                                    resultBlock: { (asset:ALAsset!) -> Void in
                                        var yes:Bool? = groupToAddTo?.addAsset(asset);
                                        if (yes == true){
                                            handler(success: true);
                                        }
                                    },
                                    failureBlock: { (error2:NSError!) -> Void in
                                        print("Failed to add asset");
                                        handler(success: false);
                                });
                            }
                        });
                    }
                } /*Group Is Not nil*/
            },
            failureBlock: { (error:NSError!) -> Void in
                print("Failed to find group");
                handler(success: false);
        });

        }, failureBlock: { (error:NSError!) -> Void in
            print("Failed to create \(error)");
            handler(success: false);
    });
}

call this method as :--

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]){

    var image:UIImage = info[UIImagePickerControllerOriginalImage] as UIImage;
    var metadata:NSDictionary = info[UIImagePickerControllerMediaMetadata] as NSDictionary;

    self.addImage(image, metaData: metadata, toAlbum: "SwiftAlbum") { (success) -> Void in
        print("Image Added : \(success)");
    }

    picker.dismissViewControllerAnimated(true, completion: nil);
}
3
votes

The code from @Scott Allen was close but would not save the image for me the first time. So if I did not already have the album created, the image would not save. My solution was to move this snippet that creates the album to the app delegate didFinishLaunchingWithOptions:

    NSString *albumName=@"album name";
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library addAssetsGroupAlbumWithName:albumName
                         resultBlock:^(ALAssetsGroup *group) {
                             NSLog(@"added album:%@", albumName);
                         }
                        failureBlock:^(NSError *error) {
                            NSLog(@"error adding album");
                        }];
2
votes

I haven't seen really clear answers and code to these questions. For me, I wanted to make sure that the album was found or created, before I fire up the camera. This code seems to be about right, and I think it is a bit cleaner and easier to steal^H^H^H^H^Hstart from.

// find or create our photo album.  If either works
// we fire up the camera. Crazy asynchronous code here.

__weak PhotoVC *weakSelf = self;
__block BOOL found = NO;

ALAssetsLibraryGroupsEnumerationResultsBlock
assetGroupEnumerator = ^(ALAssetsGroup *group, BOOL *stop){
    if (group) {
        NSString *thisGroup = [group valueForProperty:ALAssetsGroupPropertyName];
        if ([album isEqualToString:thisGroup]) {
            NSLog(@"album found!");
            [weakSelf startCamera: group];
            *stop = YES;
            found = YES;
        }
    } else { // not found, create the album
        if (found)
            return;
        NSLog(@"album not found, try making album");

        ALAssetsLibraryGroupResultBlock addGroup =
        ^(ALAssetsGroup *group){
            NSLog(@"album created");
            [weakSelf startCamera: group];
        };

        ALAssetsLibraryAccessFailureBlock addGroupFailed =
        ^(NSError *err){
            NSLog(@"add group failed: %@", [err localizedDescription]);
        };

        [library addAssetsGroupAlbumWithName:album resultBlock:addGroup failureBlock:addGroupFailed];
    }
};

[library enumerateGroupsWithTypes:ALAssetsGroupAlbum
                       usingBlock:assetGroupEnumerator
                     failureBlock:^(NSError *error) {
                         NSLog(@"album access denied");
                     }];

Tom Duff's law: steal code whenever you can.