25

This code worked fine in iOS 7 but in iOS 8.1 all assets located in the "My Photo Stream" album are nil from within the result block. (The failureBlock is not called.) Regular albums and shared albums work just fine.

I tried the accepted answer from: Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

That is, I'm holding a reference to an ALAssetsLibrary object, listening for the ALAssetsLibraryChangedNotification event (which doesn't happen btw, but oh well.) I made sure my app has permission to access photos, I'm on wi-fi, I see the photos' thumbnails just fine in my tableView. It's just when I try to load them with assetForURL: they're always nil.

// example URL: assets-library://asset/asset.JPG?id=1ECB69B9-DC7A-45A7-B135-F43317D3412C&ext=JPG
[self.library assetForURL:[NSURL URLWithString:url] resultBlock:^(ALAsset *asset) {
    NSLog(@"Asset: %@", asset); // nil :(
} failureBlock:^(NSError *error) {
    NSLog(@"Failure, wahhh!");
}];

Is anyone else seeing this issue?

Community
  • 1
  • 1
taber
  • 3,096
  • 3
  • 43
  • 68

5 Answers5

30

I had the same problem. Switching to Photos framework is not an option for me at this moment, but fortunately I have found a workaround. You may find it a big ugly and I suspect it may work slow when Photo Stream contains a lot of photos, but it is better than nothing.

The idea is to enumerate all items in the Photo Stream asset group and compare the necessary URL with the URL of each item. Fortunately, it still works.

I have a method like this (library is ALAssetsLibrary property of the same class, you may need to initialise it inside this code):

- (void)loadItem:(NSURL *)url withSuccessBlock:(void (^)(void))successBlock andFailureBlock:(void (^)(void))failureBlock {

[library assetForURL:url
        resultBlock:^(ALAsset *asset)
        {
            if (asset){
                //////////////////////////////////////////////////////
                // SUCCESS POINT #1 - asset is what we are looking for 
                //////////////////////////////////////////////////////
                successBlock();
            }
            else {
                // On iOS 8.1 [library assetForUrl] Photo Streams always returns nil. Try to obtain it in an alternative way

                [library enumerateGroupsWithTypes:ALAssetsGroupPhotoStream
                                       usingBlock:^(ALAssetsGroup *group, BOOL *stop)
                 {
                     [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
                         if([result.defaultRepresentation.url isEqual:url])
                         {
                             ///////////////////////////////////////////////////////
                             // SUCCESS POINT #2 - result is what we are looking for
                             ///////////////////////////////////////////////////////
                             successBlock();
                             *stop = YES;
                         }
                     }];
                 }

                                     failureBlock:^(NSError *error)
                 {
                     NSLog(@"Error: Cannot load asset from photo stream - %@", [error localizedDescription]);
                     failureBlock();

                 }];
            }

        }
        failureBlock:^(NSError *error)
        {
            NSLog(@"Error: Cannot load asset - %@", [error localizedDescription]);
            failureBlock();
        }
  ];
}

Hope this helps.

Andrew Simontsev
  • 988
  • 7
  • 17
  • 3
    While not ideal this does indeed do the trick, thanks a ton! Not to be overly nitpicky but it looks like `item.url` should be just `url` correct? And we'd probably need to call a success block that passes in the ALAsset. Thanks again! – taber Oct 23 '14 at 14:28
  • I do have the same issue for both Moment and My Photo Stream, although the solution above does not work...enumerating groupes does nothing...even if I change ALAssetsGroupPhotoStream to ALAssetsGroupAll – AkademiksQc Oct 23 '14 at 18:42
  • strange, it worked over here - are you sure you approved permission to photos? – taber Oct 23 '14 at 18:59
  • @taber You are right! I copied it from my project and that code was too complicated. I forgot to rename item.url there and omitted successBlock code when inserted a SUCCESS POINT comment. I have edited my answer and fixed mistakes. – Andrew Simontsev Oct 25 '14 at 05:26
  • That solution doesn't work for me either, as the asset is also nil in Success Point #2. – Anas Oct 29 '14 at 17:40
  • 1
    @anas, in the SUCCESS POINT #2 it arrives to the `result` variable, not `asset`. – Andrew Simontsev Oct 30 '14 at 03:56
  • Where do we use this method. ?? – Sushil Sharma Jan 07 '15 at 06:38
  • You can just add it as a private method to the class where you need to load a picture from an asset library and call it instead of the assetForURL. The required asset may appear in two places marked here as SUCCESS POINT #1 (in the asset variable) and SUCCESS POINT #2 (in the result variable). I think it makes sense to change this code a little bit and pass asset/result to the successBlock. But it is up to you. – Andrew Simontsev Jan 08 '15 at 05:16
2

From iOS 8.0 and later, Apple suggests to use Photos framework instead of the Assets Library framework.

enter image description here

Ratan
  • 301
  • 2
  • 11
  • 2
    It doesn't say that we “have to use” Photos.framework. The just tell us that this is the way to access pictures in the future. – herzi Aug 30 '15 at 10:23
2

I've made the observation that trying to retrieve an asset using assetForURL inside a writeImage toSavedPhotosAlbum success block for that same asset will yield an asset of nil (most of the time).

However, retrieving the asset with assetForURL some time after the writeImage success block has completed execution does yield the correct asset.

Waiting for 1 second did work, while waiting for only 300 ms did not. But this of course will be different for each and every device and situation.

This does not really answer the question in a satisfying way, but maybe it helps someone else figuring out the underlying problem.

Thomas Hilbert
  • 3,438
  • 2
  • 11
  • 32
  • Experiencing the same issue. Even though the write has successfully resolved it's returning nil in the assetForURL request. Curious about whats actually happening. – rsiemens Jan 30 '18 at 23:49
1

Tested with iPad mini on iOS 8.1, this is how you should do it with the new Photos Framework:

NSURL *url = /* your asset url from the old ALAsset library prior to iOS 8 */
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithALAssetURLs:@[url]
                                                               options:nil];
assert(assets.count == 1);
PHAsset *asset = assets.firstObject;

[[PHImageManager defaultManager] requestImageForAsset:asset
                   targetSize:CGSizeMake(800, 800) // TODO: your target size
                  contentMode:PHImageContentModeDefault
                      options:nil
                resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info)
 {
     // Do whatever you want to the result
 }];
Yuchen
  • 24,092
  • 18
  • 133
  • 193
-1

After not finding any answers for this anywhere, I created the following extension to PHAsset which works great as of iOS 8.2 although I assume it's theoretically slow. Even though one of the prior comments says that this is fixed on iOS8.2 beta, the bug was still present for me now that iOS8.2 is released.

import Photos
import UIKit

extension PHAsset {
    class func fetchAssetWithALAssetURL (alURL: NSURL) -> PHAsset? {
        let phPhotoLibrary = PHPhotoLibrary.sharedPhotoLibrary()
        let assetManager = PHImageManager()
        var phAsset : PHAsset?

        let optionsForFetch = PHFetchOptions()
        optionsForFetch.includeHiddenAssets = true

        var fetchResult = PHAsset.fetchAssetsWithALAssetURLs([alURL], options: optionsForFetch)
        if fetchResult?.count > 0 {
            return fetchResult[0] as? PHAsset
        } else {
            var str = alURL.absoluteString!
            let startOfString = advance(find(str, "=")!, 1)
            let endOfString = advance(startOfString, 36)
            let range = Range<String.Index>(start:startOfString, end:endOfString)
            let localIDFragment = str.substringWithRange(range)
            let fetchResultForPhotostream = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.Album, subtype: PHAssetCollectionSubtype.AlbumMyPhotoStream, options: nil)
            if fetchResultForPhotostream?.count > 0 {
                let photostream = fetchResultForPhotostream![0] as PHAssetCollection
                let fetchResultForPhotostreamAssets = PHAsset.fetchAssetsInAssetCollection(photostream, options: optionsForFetch)
                if fetchResultForPhotostreamAssets?.count >= 0 {
                    var stop : Bool = false
                    for var i = 0; i < fetchResultForPhotostreamAssets.count && !stop; i++ {
                        let phAssetBeingCompared = fetchResultForPhotostreamAssets[i] as PHAsset
                        if phAssetBeingCompared.localIdentifier.rangeOfString(localIDFragment, options: nil, range: nil, locale: nil) != nil {
                            phAsset = phAssetBeingCompared
                            stop = true
                        }
                    }
                    return phAsset
                }
            }
            return nil
        }
    }
}