Need assistance regarding ALAssetsLibrary enumeration - ios

[aLib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetsGroupEnumerationBlock failureBlock:failureBLock];
This method enumerate every group, I want to enumerate for first group only then I want to break it. My purpose is to ask for permission that iOS pop for first time. I am doing no additional work, I have notifications in blocks that notify and trigger other required functionality. But multiple group enumeration trigger notification multiple times and that I want to stop.
Here is my enumeration block with stop parameter
void(^assetsGroupEnumerationBlock)(ALAssetsGroup*, BOOL*) = ^(ALAssetsGroup *groups, BOOL *stop) {
*stop = YES;
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
};
But notification is getting called two times I see nslog twice in console.

Use the stop parameter:
[lib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
*stop = YES;
if (group) {
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
}
} failureBlock:^(NSError *error) {
// denied
}];

Related

Load images from user photo library and update UICollectionView

So i have UICollectionView. I need to get all user photos from his photos library and when photo is loaded update my collection view. I tried using NSNotificationCenter. I added observer to UICollectionViewController and when i enumerate photos i post notification so i could update my collection view when photo us loaded. Here is my controller:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(phonePhotoLoaded:) name:#"PhonePhotoLoaded" object:nil];
[self.manager getPhonePhotos];
Selector method:
-(void)phonePhotoLoaded:(NSNotification *)userInfo
{
[self.collectionView performBatchUpdates:^{
NSDictionary *dic = [userInfo valueForKey:#"userInfo"];
[self.photos addObject:[dic valueForKey:#"photo"]];
[self.collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:self.photos.count inSection:0]]];
} completion:^(BOOL finished){
}];
}
And in [self.manager getPhonePhotos]
ALAssetsLibrary *al = [PhotoManager defaultAssetsLibrary];
[al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:^(ALAssetsGroup *group, BOOL *stop){
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
if ([[asset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
NSDictionary *userPhotos = [NSDictionary dictionaryWithObjectsAndKeys:asset, #"photo", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PhonePhotoLoaded" object:self userInfo:userPhotos];
}
}];
}failureBlock:^(NSError *error) {
;}
];
This is what error i get:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'too many update animations on one view - limit is 31 in flight at a time (; }; layer = >)'
So as much as i understand, when i enumerate photos, i post notification, then notification method is executed but UICollectionView is not making animations. He somehow starts them when i finish enumerating. So how could i update my UI when photo is fetched? Any ideas?
Don't post a notification on each iteration of the photo enumeration (each time your block is called). Instead, build up an array of all of the assets and post it on the last call of the block (IIRC, the asset will be nil on the last call, need to check the docs).
Your current problem is that you are starting lots of animations by posting a notification each time and you should really collate all of your changes and then execute a single performBatchUpdates.

Why doesn't ALAssetLibrary -enumerateGroupsWithTypes:usingBlock: "stop" when I tell it to?

I noticed that even though I set the *stop BOOL pointer to YES, my enumeration block is being executed twice. I thought setting *stop = YES would stop after the first one?
[lib enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"Enumerating a group");
//should stop after first?
*stop = YES;
} failureBlock:^(NSError *error) {
}];
The logs:
2014-03-05 12:27:29:363 AppName[3625:1547] Enumerating a group
2014-03-05 12:27:29:379 AppName[3625:1547] Enumerating a group
From the documentation, -enumerateGroupsWithTypes:usingBlock: is asynchronous, but is it also automatically parallelized? Both enumerations happen on the main thread, so maybe I should enforce my own stop? Seems weird to offer stop if it's not exactly enforced in the way the documentation suggests.
EDIT
For what it's worth, I added my own enforcement and it works. But why do I need to do this?
__block BOOL stopItForReal = NO;
[lib enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"Enumerating a group");
//does not stop enumeration
*stop = YES;
//actually stops enumeration. I win, runtime!
if (stopItForReal)
return;
stopItForReal = YES;
} failureBlock:^(NSError *error) {
}];
When group enumeration is terminated, ALAssetsLibraryGroupsEnumerationResultsBlock is invoked with group = nil. So you can write something like:
if (group != nil)
{
// your code.
}

Can't add ALAssets to NSOperationQueue from enumeration block

I'm trying to enumerate through the ALAssetLibrary to retrieve all saved photos. From the enumeration block, I'm trying to to send each ALAsset for asynchronous processing by passing it to an NSInvocationOperation object, then adding that to an NSOperationQueue. However, only the first ALAsset object is properly passed to the processing method. All subsequent assets are just passed as nil.
Here is my code:
ViewDidLoad:
queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (! result) {
return;
}
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(processAsset:) object:result];
[queue addOperation:operation];
return;
}];
} failureBlock:^(NSError *error) {
NSLog(#"%#", error);
}];
And the -processAsset method
- (void)processAsset:(ALAsset *)asset
{
NSLog(#"Asset: %#", asset);
// Asset is nil after the first iteration
}
Any help would be really appreciated, thanks!
All ALAssets objects are deallocated when the ALAssetsLibrary that obtained them goes out of scope. If you want to do what you're doing, you'll need to keep a strong reference to library, and then deallocate library when you're done with it.
(In your case, they deallocate at the end of viewDidLoad.)

Returning NSArray of UIImages from ALAssetsLibrary

I'm working on photo app and here is the deal:
I have created custom photo album an added some photos to it (users can create many albums). I used ALAssetsLibrary for this and CustomPhotoAlbum category. Everything worked flawlessly until I needed to get those photos back to reuse them.
So Ive created custom metod for this category and I'm saving UIImages to NSMutableArray but I want this method to return this array outside... My problem is that enumerateAssetsUsingBlock: is running in background and saving photos to album while I want to access them! How can I do something for this method to wait until every photo is loaded to album and then return NSArray?
Here is my method:
- (void)loadImagesFromAlbumNamed:(NSString *)name
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
[self enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group == nil) {
return;
}
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:name]) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result == nil) {
return;
}
// NSLog(#"%#", result);
UIImage *image = [UIImage imageWithCGImage:(__bridge CGImageRef)([result defaultRepresentation])];
[photos addObject:image];
}];
NSLog(#"%#", [photos description]);
}
} failureBlock:^(NSError *error) {
NSLog(#"%#", [error localizedDescription]);
}];
}
I hope you get my point of view... If not, just ask and I'll try to explain it better way!
Don't try to wait. Instead, pass a block to your method which should be called when all of the asset processing blocks are completed and will pass the array of images back to the caller. The caller should not block, you just want to structure your method to deal with the asynchronous nature of what you're trying to do.

The execution of block is always delayed

I'm pretty new to IOS. I want to get all pictures on a device in viewDidLoad. but the block is always executed after I called NSLog(#"%d", photos.count) in the code as follows. How to handle such a case?
__block NSMutableArray* photos = [[[NSMutableArray alloc] init] retain];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
void (^assertsEnumerator)(ALAsset *, NSUInteger , BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result)
{
NSLog(#"Assert name : %#", [result valueForProperty:ALAssetPropertyURLs]);
[photos addObject:[UIImage imageWithCGImage:[result aspectRatioThumbnail]]];
}
};
void (^groupEnumerator)(ALAssetsGroup*, BOOL*) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
NSLog(#"Adding group %#", [group valueForProperty:ALAssetsGroupPropertyName]);
[group enumerateAssetsUsingBlock: assertsEnumerator];
}
};
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock:groupEnumerator
failureBlock:^(NSError * err) {NSLog(#"Erorr: %#", [err localizedDescription]);}];
[library release];
NSLog(#"%d", photos.count);
[photos release];
As the ALAssetsLibrary documentation states, enumerateGroupsWithTypes:usingBlock:failureBlock: is asynchronous, so instead of being called in place, it's scheduled to be called from within the next run loop cycle. The documentation states clearly what's the reason for that and how you should proceed:
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.

Resources