19

I have run into an issue on iOS 8 with the Assets Library framework that appears to be a bug in iOS 8. If I create an album called 'MyMedia' and then delete it, then when I try to create the album again, this chunk of code below returns 'nil' indicating that the album 'MyMedia' exists even though it does not because I deleted it using the 'Photos' app.

__block ALAssetsGroup *myGroup = nil;
__block BOOL addAssetDone = false;
NSString *albumName = @"MyMedia";
[assetsLib addAssetsGroupAlbumWithName:albumName
                           resultBlock:^(ALAssetsGroup *group) {
                               myGroup = group;
                               addAssetDone = true;
                           } failureBlock:^(NSError *error) {
                               NSLog( @"failed to create album: %@", albumName);
                               addAssetDone = true;
                           }];

while (!addAssetDone) {
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05f]];
}
return myGroup; // returns nil if group has previously been created and then deleted

This same method works when creating a brand new album 'MyMedia2.' Has anyone else experienced this issue and know of a workaround or solution? Is the only solution to move to the new 'Photos' framework or am I doing something incorrect here? Note that this code always works on iOS7.X

Actually the steps to reproduce this problem are as follows -> 1. Uninstall your app that takes photos and saves them to a custom album 2. Under iOS Photos delete the custom album that has saved photos in it 3. Install your app 4. If you take pictures or record videos with the app it does not create them or store them. If you look under iOS Photos albums the custom album one does not exist and none of the pictures/videos taken with the app exist.

Adam Freeman
  • 1,171
  • 9
  • 18
  • You probably want to start coding to the Photos Framework. I just, what a pain... – Paul Cezanne Sep 23 '14 at 23:18
  • If I build the app with Xcode 6 and the Photos framework can I still run it on devices with 7.X installed? – Adam Freeman Sep 23 '14 at 23:43
  • 1
    No, it is iOS8 only. And yes, this makes it difficult, essentially you have to code with both APIs – Paul Cezanne Sep 24 '14 at 11:26
  • Got same issue, it sucks! – Kjuly Sep 27 '14 at 15:12
  • Same problem? Can I delete the asset at all or will I never be able to recreate an album with that name? – oarfish Aug 07 '15 at 15:27
  • You cannot delete the asset using the Assets Library framework because Apple does not permit it for some reason (only the Photos app was permitted to delete assets using the Assets Library framework.) But you can delete the asset using the Photos framework. You can create multiple albums with the exact same name including on iCloud. – Adam Freeman Aug 09 '15 at 01:10

6 Answers6

11

My previous answer was incorrect. I had not really tested it out. I did finally figure out what had to be done and it was difficult but I got it to work. This is what I had to do to get my app to run on both iOS 7.x.X and iOS 8.X.x and create a custom album that had been previously deleted by the app -->

  1. I wrote two chunks of code: one that uses the Photos framework on iOS 8.x.x and one that uses the AssetsLibrary framework on iOS 7.x.x

  2. Sp the app could run on both iOS versions, I linked the app to the Photos framework but then changed it from required to optional so it would not be loaded on iOS 7.x.x

  3. Because the Photos framework code could not be called directly at runtime on iOS 7.x.x, I had to change it so it loaded the classes, functions (and blocks!) dynamically at runtime

Here is the code chunk that works when running on an iPhone. This should work in the simulator too -->

// PHPhotoLibrary_class will only be non-nil on iOS 8.x.x
Class PHPhotoLibrary_class = NSClassFromString(@"PHPhotoLibrary");

if (PHPhotoLibrary_class) {

   /**
    *
    iOS 8..x. . code that has to be called dynamically at runtime and will not link on iOS 7.x.x ...

    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
    } completionHandler:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Error creating album: %@", error);
        }
    }];
    */

    // dynamic runtime code for code chunk listed above            
    id sharedPhotoLibrary = [PHPhotoLibrary_class performSelector:NSSelectorFromString(@"sharedPhotoLibrary")];

    SEL performChanges = NSSelectorFromString(@"performChanges:completionHandler:");

    NSMethodSignature *methodSig = [sharedPhotoLibrary methodSignatureForSelector:performChanges];

    NSInvocation* inv = [NSInvocation invocationWithMethodSignature:methodSig];
    [inv setTarget:sharedPhotoLibrary];
    [inv setSelector:performChanges];

    void(^firstBlock)() = ^void() {
        Class PHAssetCollectionChangeRequest_class = NSClassFromString(@"PHAssetCollectionChangeRequest");
        SEL creationRequestForAssetCollectionWithTitle = NSSelectorFromString(@"creationRequestForAssetCollectionWithTitle:");
        [PHAssetCollectionChangeRequest_class performSelector:creationRequestForAssetCollectionWithTitle withObject:albumName];

    };

    void (^secondBlock)(BOOL success, NSError *error) = ^void(BOOL success, NSError *error) {
       if (success) {
           [assetsLib enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
               if (group) {
                   NSString *name = [group valueForProperty:ALAssetsGroupPropertyName];
                   if ([albumName isEqualToString:name]) {
                       groupFound = true;
                       handler(group, nil);
                   }
               }
           } failureBlock:^(NSError *error) {
               handler(nil, error);
           }];
       }

       if (error) {
           NSLog(@"Error creating album: %@", error);
           handler(nil, error);
       }
   };

   // Set the success and failure blocks.
   [inv setArgument:&firstBlock atIndex:2];
   [inv setArgument:&secondBlock atIndex:3];

   [inv invoke];

}
else {   
   // code that always creates an album on iOS 7.x.x but fails
   // in certain situations such as if album has been deleted
   // previously on iOS 8...x. .              
   [assetsLib addAssetsGroupAlbumWithName:albumName
       resultBlock:^(ALAssetsGroup *group) {
       handler(group, nil);
   } failureBlock:^(NSError *error) {
       NSLog( @"Failed to create album: %@", albumName);
       handler(nil, error);
   }];
}
Adam Freeman
  • 1,171
  • 9
  • 18
  • @Kjuly : handler(group, nil); give me declaration error please give me declaration code. – Chirag Pipaliya Nov 26 '14 at 05:43
  • 2
    @ChiragPipaliya maybe u can try the lib: https://github.com/Kjuly/ALAssetsLibrary-CustomPhotoAlbum. Adam has already merged this fix for iOS 8. :) – Kjuly Nov 26 '14 at 06:44
  • handler(group, nil) is a completion block that you provide to function as a parameter with method signature: ^(void)(ALAssetsGroup*, NSError*) – Adam Freeman Dec 01 '14 at 20:33
  • 1
    I would advise against using NSSelectorFromString and just using @selector({method name goes here}). That way you aren't susceptible to typos. Let the compiler help you! – Brian Sachetta Jan 06 '15 at 16:26
  • @AdamFreeman Have you try to get all photos like moment wise in ios7? I got all photos but not getting album creation date and event name. I want to developer my app like it shows all moment wise data like we see in iPhoto app in ios8. In ios8 it is showing all moments and its working fine, but in ios7 its failed to get photos using PHAsset. – Pratik Patel Jan 09 '15 at 09:54
  • The code on iOS 7 is for a custom album but not for the moments albums. I am not sure how to add photos to the moments albums but I think they just have a different collection type and/or album name. – Adam Freeman Jan 10 '15 at 20:22
  • Is there any way to check if the album already exists before we create it in "firstBlock"? When I run this code, the same custom album gets created as many times as I launch my app (obviously this is not desired). I'd just like to say: If album doesn't exist, create one; otherwise, do nothing. Any solutions? – Friendly King Jul 22 '15 at 23:48
3

Using Adam's answer, and Marin Todorov's Category on ALAssetsLibrary, ALAssetsLibrary+CustomPhotoAlbum to create photo Albums, and place photos in them, this code below replaces the main workHorse in that Category, it works on both iOS7 devices and iOS 8.1 devices for those who need to have both.

it gives two warnings about performSelector on unknown class though, any improvements are appreciated:

(it will not copy a photo from a shared album that you did not create and will fail with message, any enhancements there also would be good)

1) add the "Photos" Frameworks, set to "optional"

2) include the import line #import < Photos/PHPhotoLibrary.h >

    //----------------------------------------------------------------------------------------
- (void)addAssetURL:(NSURL *)assetURL
            toAlbum:(NSString *)albumName
         completion:(ALAssetsLibraryWriteImageCompletionBlock)completion
            failure:(ALAssetsLibraryAccessFailureBlock)failure
{
NSLog();
    __block BOOL albumWasFound = NO;

    //-----------------------------------------
    ALAssetsLibraryGroupsEnumerationResultsBlock enumerationBlock;
    enumerationBlock = ^(ALAssetsGroup *group, BOOL *stop)
    {
NSLog(@"  ALAssetsLibraryGroupsEnumerationResultsBlock");
        // Compare the names of the albums
        if ([albumName compare:[group valueForProperty:ALAssetsGroupPropertyName]] == NSOrderedSame)
        {
NSLog(@"--------------Target album is found");
            // Target album is found
            albumWasFound = YES;

            // Get a hold of the photo's asset instance
            // If the user denies access to the application, or if no application is allowed to
            //   access the data, the failure block is called.
            ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
            [self _assetForURLResultBlockWithGroup:group
                                          assetURL:assetURL
                                        completion:completion
                                           failure:failure];

            [self assetForURL:assetURL
                resultBlock:assetForURLResultBlock
               failureBlock:failure];

            // Album was found, bail out of the method
            *stop = YES;
        }

        if (group == nil && albumWasFound == NO)
        {
NSLog(@"--------------Target album does not exist");
            // Photo albums are over, target album does not exist, thus create it

            // Since you use the assets library inside the block,
            //   ARC will complain on compile time that there’s a retain cycle.
            //   When you have this – you just make a weak copy of your object.
            ALAssetsLibrary * __weak weakSelf = self;

            // If iOS version is lower than 5.0, throw a warning message
            if (! [self respondsToSelector:@selector(addAssetsGroupAlbumWithName:resultBlock:failureBlock:)])
            {
NSLog(@"--------------Target album does not exist and does not respond to addAssetsGroupAlbumWithName");
            } else {
NSLog(@"--------------Target album does not exist addAssetsGroupAlbumWithName");

                // -----------   PHPhotoLibrary_class will only be non-nil on iOS 8.x.x  -----------
                Class PHPhotoLibrary_class = NSClassFromString(@"PHPhotoLibrary");
NSLog(@"PHPhotoLibrary_class %@ ", PHPhotoLibrary_class);

                if (PHPhotoLibrary_class)
                {
NSLog(@"iOS8");

                    // ---------  dynamic runtime code  -----------
                    id sharedPhotoLibrary = [PHPhotoLibrary_class performSelector:NSSelectorFromString(@"sharedPhotoLibrary")];
NSLog(@"sharedPhotoLibrary %@ ", sharedPhotoLibrary);

                    SEL performChanges = NSSelectorFromString(@"performChanges:completionHandler:");

                    NSMethodSignature *methodSig = [sharedPhotoLibrary methodSignatureForSelector:performChanges];

                    NSInvocation* inv = [NSInvocation invocationWithMethodSignature:methodSig];
                    [inv setTarget:sharedPhotoLibrary];
                    [inv setSelector:performChanges];

                    void(^firstBlock)() = ^void()
                    {
NSLog(@"firstBlock");
                        Class PHAssetCollectionChangeRequest_class = NSClassFromString(@"PHAssetCollectionChangeRequest");
                        SEL creationRequestForAssetCollectionWithTitle = NSSelectorFromString(@"creationRequestForAssetCollectionWithTitle:");
NSLog(@"PHAssetCollectionChangeRequest_class %@ ", PHAssetCollectionChangeRequest_class);


                        [PHAssetCollectionChangeRequest_class performSelector:creationRequestForAssetCollectionWithTitle withObject:albumName];

                    };

                    void (^secondBlock)(BOOL success, NSError *error) = ^void(BOOL success, NSError *error)
                    {
NSLog(@"secondBlock");
                       if (success)
                       {
NSLog(@"success");
                            [self enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *fullStop)
                            {
                               if (group)
                               {
NSLog(@"group %@ ", group);
                                   NSString *name = [group valueForProperty:ALAssetsGroupPropertyName];
                                   if ([albumName isEqualToString:name])
                                   {
NSLog(@"[albumName isEqualToString:name] %@ ", name);
                                        ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
                                        [self _assetForURLResultBlockWithGroup:group
                                                                      assetURL:assetURL
                                                                    completion:completion
                                                                       failure:failure];

                                        [self assetForURL:assetURL
                                            resultBlock:assetForURLResultBlock
                                           failureBlock:failure];

                                        *fullStop = YES;
                                   }
                               }
                            } failureBlock:failure];
                       }

                       if (error)
                       {
NSLog(@"Error creating album: %@", error);
                       }
                   };

                   // Set the success and failure blocks.
                   [inv setArgument:&firstBlock atIndex:2];
                   [inv setArgument:&secondBlock atIndex:3];

                   [inv invoke];

                } else {
NSLog(@"iOS7");
                    [self addAssetsGroupAlbumWithName:albumName resultBlock:^(ALAssetsGroup *createdGroup)
                    {
                        // Get the photo's instance, add the photo to the newly created album
                        ALAssetsLibraryAssetForURLResultBlock assetForURLResultBlock =
                            [weakSelf _assetForURLResultBlockWithGroup:createdGroup
                                                            assetURL:assetURL
                                                          completion:completion
                                                             failure:failure];

                        [weakSelf assetForURL:assetURL
                                  resultBlock:assetForURLResultBlock
                                 failureBlock:failure];
                    }
                    failureBlock:failure];
                }
            }
            // Should be the last iteration anyway, but just in case
            *stop = YES;
        }
    };



    // Search all photo albums in the library
    [self enumerateGroupsWithTypes:ALAssetsGroupAlbum
                  usingBlock:enumerationBlock
                failureBlock:failure];
}
hokkuk
  • 675
  • 5
  • 21
2

You can try My below Method for Create Album for iOS 7 and iOS 8

#define PHOTO_ALBUM_NAME @"AlbumName Videos"
#pragma mark - Create Album
-(void)createAlbum{

// PHPhotoLibrary_class will only be non-nil on iOS 8.x.x
Class PHPhotoLibrary_class = NSClassFromString(@"PHPhotoLibrary");

if (PHPhotoLibrary_class) {


    // iOS 8..x. . code that has to be called dynamically at runtime and will not link on iOS 7.x.x ...

    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:PHOTO_ALBUM_NAME];
    } completionHandler:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Error creating album: %@", error);
        }else{
            NSLog(@"Created");
        }
    }];
}else{
    [self.library addAssetsGroupAlbumWithName:PHOTO_ALBUM_NAME resultBlock:^(ALAssetsGroup *group) {
        NSLog(@"adding album:'Compressed Videos', success: %s", group.editable ? "YES" : "NO");

        if (group.editable == NO) {
        }

    } failureBlock:^(NSError *error) {
        NSLog(@"error adding album");
    }];
}}
Gaurav
  • 178
  • 12
1

Just wanted to update everyone I should have updated sooner but I got kind of swamped with work. This issue is/was an issue with iOS 8 but has been fixed with iOS 8.0.2 so all you need to do to fix it is update your iOS to iOS 8.0.2

Adam Freeman
  • 1,171
  • 9
  • 18
  • Thanks dude. I was pulling my hair out. But I still have the same problem with running in Xcode 6.0.1 with iOS 8 iPhone 6 simulator. How should I fix that? – Sam B Oct 08 '14 at 03:18
  • 1
    Facing same in 8.3, are you facing same? – Bhumit Mehta May 26 '15 at 10:42
  • I have not tested with 8.3 yet. What are you seeing on 8.3? After a bunch of more testing and using different assets library framework functions, the only code that really had to be changed for 8.0 8.0.1 and 8.0.2 was the creation of a custom album. I don't remember what the original AL function was that I used that did not work but I just wound up using some other functions that did work and that solved all of my other problems other than creating a custom album. – Adam Freeman May 26 '15 at 16:56
1

I used the below code to check whether a specific album exists, and if it does not exist, create it and add a couple of images to it. After creating an Asset from a UIImage, I use its placeholder to add it to the album without leaving the block.

//Will enter only in iOS 8+
Class PHPhotoLibrary_class = NSClassFromString(@"PHPhotoLibrary");

if (PHPhotoLibrary_class)
{
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^
    {
        //Checks for App Photo Album and creates it if it doesn't exist
        PHFetchOptions *fetchOptions = [PHFetchOptions new];
        fetchOptions.predicate = [NSPredicate predicateWithFormat:@"title == %@", kAppAlbumName];
        PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:fetchOptions];

        if (fetchResult.count == 0)
        {
            //Create Album
            PHAssetCollectionChangeRequest *albumRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:kAppAlbumName];

            //Add default photos to it
            NSMutableArray *photoAssets = [[NSMutableArray alloc] init];

            for (UIImage *image in albumDefaultImages)
            {
                PHAssetChangeRequest *imageRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
                [photoAssets addObject:imageRequest.placeholderForCreatedAsset];
            }

            [albumRequest addAssets:photoAssets];
        }
    }
    completionHandler:^(BOOL success, NSError *error)
    {
        NSLog(@"Log here...");
    }];
}
Shaked Sayag
  • 4,774
  • 2
  • 26
  • 34
0

As none of the above suggestions helped me, this is how I went about solving the issues with saving assets (photos) to a custom album name. This code: "fetchCollectionResult.count==0" specifically handles the situation when you have deleted your custom album once and trying to save to it again, as I suppose fetchCollectionResult might stop being 'nil'. You can easily change this to support saving of videos/movies too.

This code is for iOS 8 only! You must make sure not to call it if the device is running on earlier versions!

#define PHOTO_ALBUM_NAME @"MyPhotoAlbum"

NSString* existingAlbumIdentifier = nil;

-(void)saveAssetToAlbum:(UIImage*)myPhoto
{
    PHPhotoLibrary* photoLib = [PHPhotoLibrary sharedPhotoLibrary];

    __block NSString* albumIdentifier = existingAlbumIdentifier;
    __block PHAssetCollectionChangeRequest* collectionRequest;

    [photoLib performChanges:^
     {
         PHFetchResult* fetchCollectionResult;
         if ( albumIdentifier )
             fetchCollectionResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[albumIdentifier] options:nil];

         // Create a new album
         if ( !fetchCollectionResult || fetchCollectionResult.count==0 )
         {
             NSLog(@"Creating a new album.");
             collectionRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:PHOTO_ALBUM_NAME];
             albumIdentifier = collectionRequest.placeholderForCreatedAssetCollection.localIdentifier;
         }
         // Use existing album
         else
         {
             NSLog(@"Fetching existing album, of #%d albums found.", fetchCollectionResult.count);
             PHAssetCollection* exisitingCollection = fetchCollectionResult.firstObject;
             collectionRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:exisitingCollection];
         }

         NSLog(@"Album local identifier = %@", albumIdentifier);

         PHAssetChangeRequest* createAssetRequest;
         createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:myPhoto];

         [collectionRequest addAssets:@[createAssetRequest.placeholderForCreatedAsset]];
     }
           completionHandler:^(BOOL success, NSError *error)
     {
         if (success)
         {
             existingAlbumIdentifier = albumIdentifier;
             NSLog(@"added image to album:%@", PHOTO_ALBUM_NAME);
         }
         else
             NSLog(@"Error adding image to  album: %@", error);
     }];
}
gStation
  • 73
  • 1
  • 6