Retrieve the names of photos albums (ALAssetsGroup) in Photos.app crash - ios

I'm trying to get all the albums names. here is what I do.
The method [group valueForKey:ALAssetsGroupPropertyName] crashes
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos | ALAssetsGroupAlbum
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (nil != group)
{
NSLog(#"name %#",[group valueForKey:ALAssetsGroupPropertyName]);
}
*stop = NO;
} failureBlock:^(NSError *error) {
NSLog(#"error: %#", error);
}];
I really don't know why it crashes, if someone knows, I'll really appreciate some help !
Here is the crash log :
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<ALAssetsGroup 0x17801ff90> valueForUndefinedKey:]: this class is not key value coding-compliant for the key ALAssetsGroupPropertyName.`

You are calling the wrong method. Change this:
NSLog(#"name %#",[group valueForKey:ALAssetsGroupPropertyName]);
to:
NSLog(#"name %#",[group valueForProperty:ALAssetsGroupPropertyName]);

Related

Adding multiple assets to collection with Photo Framework - Objective-C

I have the following code:
-(void) saveFromLocationsArray: (NSArray*) locations{
[self.photoLib performChanges:^{
PHAssetChangeRequest *creationRequest;
NSMutableArray *changesArray = [[NSMutableArray alloc] init];
for(int i=0; i<locations.count; i++){
creationRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:locations[i]];
[changesArray addObject:[creationRequest placeholderForCreatedAsset]];
}
PHAssetCollectionChangeRequest *collectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:self.objcAppAlbum];
[collectionChangeRequest addAssets:changesArray];
} completionHandler:^(BOOL success, NSError *error) {
if(!success){
NSLog(#"an error has occured while trying to save pic to library: %#", error);
}
else{
NSLog(#"image was added to album ObjcApp");
}
}];
In a nutshell: I have an array of locations (URLS) of images I've just downloaded, and I'm trying to add them all to the iPhone's photo library. i managed to do so for one image (in a different method) but here it crashes on [collectionChangeRequest addAssets:changesArray], my guess is that the argument fo addAssest is invalid... but i can't figure out why.
this is the working code for one image only:
-(void) saveFromLocation: (NSURL*) location{
[self.photoLib performChanges:^{
PHAssetChangeRequest *creationRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:location];
PHAssetCollectionChangeRequest *collectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:self.objcAppAlbum];
NSArray *arr = [NSArray arrayWithObjects:[creationRequest placeholderForCreatedAsset], nil];
[collectionChangeRequest addAssets:arr];
} completionHandler:^(BOOL success, NSError *error) {
if(!success){
NSLog(#"an error has occured while trying to save pic to library: %#", error);
}
else{
NSLog(#"image was added to album ObjcApp");
}
}];
}
error: 2019-03-05 14:26:26.062368 ObjcProject[1026:371314]
-[__NSCFString path]: unrecognized selector sent to instance 0x1740fa880 2019-03-05 14:26:26.063343 ObjcProject[1026:371314] *
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFString path]:
unrecognized selector sent to instance 0x1740fa880'
* First throw call stack: (0x186d3d1b8 0x18577455c 0x186d44268 0x186d41270 0x186c3a80c 0x19276338c 0x192700ee8 0x19275e8c4
0x19275edfc 0x1914ae4b8 0x191d49c4c 0x19275e510 0x192738be4
0x1914a0ddc 0x1001dd258 0x1001dd218 0x1001eaaec 0x1001e0ce0
0x1001eb088 0x1001ece2c 0x1001ecb78 0x185dcf2a0 0x185dced8c)
libc++abi.dylib: terminating with uncaught exception of type
NSException

iOS - Bad Access when calling ALAssetsLibrary several times

When a user uploads a picture from the iPhone gallery, I extract the location using ALAssetsLibrary:
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:urlPhoto
resultBlock:^(ALAsset *asset) {
CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
} failureBlock:^(NSError *error) {
NSLog(#"Can not get asset - %#",[error localizedDescription]);
}];
However, if the user uploads a picture and then returns to the upload screen and upload another, after three or four uploads the app crashes on EXC_BAD_ACCESS, when running the assetForURL:resultBlock:failureBlock: method.
I guess it happens since the assetForURL:resultBlock:failureBlock: runs async and the ALAssetsLibrary is released for some reason, though it cannot run simultaneously the way I've built the app.
How can I prevent it from crashing?
EDIT
Although the crash was always at this point (due to the earlier dismiss), the error occurred because of a UITableView that was deallocated earlier, but at this point referred to its delegate/datasource.
the fix was adding:
- (void) dealloc
{
myTableView.dataSource = nil;
myTableView.delegate = nil;
}
at the end of the UIViewController that had the TableView.
Just change your object to property .
interface:
#property (nonatomic, strong) ALAssetsLibrary* assetslibrary;
Than call implementation:
if (self.assetslibrary == nil) {
self.assetslibrary = [[ALAssetsLibrary alloc] init];
}
[self.assetslibrary assetForURL:urlPhoto
resultBlock:^(ALAsset *asset) {
CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
} failureBlock:^(NSError *error) {
NSLog(#"Can not get asset - %#",[error localizedDescription]);
}];

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