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]);
}];
Related
I am developing a framework in iOS. I have a function in my framework that it gets the most recent image from the Camera Roll by using Photos framework. This function works successfully in most of projects which use my framework. But it crashes on only one project from these projects. I tried several ways to fix this crash, but nothing changed in this crash.
I checked this project settings but I could not find any difference from my project settings. Also I checked the gallery permission in the plist file. Also there is an interesting point. This project uses almost all functions of my library successfully but it also crashes when my library tries to reach UIImagePickerController in this project.
#interface Controller ()
{
UIBarButtonItem *barButtonItem;
}
#property(nonatomic, strong) PHFetchResult *fetchResult;
#property(nonatomic, strong) PHAsset *lastAsset;
#end
// calling this in the main thread
- (void)checkGalleryPermission
{
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusAuthorized)
{
[self setLastGalleryImageToPhotoGallery];
}
else if (status == PHAuthorizationStatusNotDetermined)
{
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status)
{
if (status == PHAuthorizationStatusAuthorized)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self setLastGalleryImageToPhotoGallery];
});
}
}];
}
}
- (void)setLastGalleryImageToPhotoGallery
{
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES]];
self.fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
if (self.fetchResult != nil)
{
self.lastAsset = [self.fetchResult lastObject];
if (self.lastAsset != nil)
{
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
imageRequestOptions.synchronous = YES; // tried NO
imageRequestOptions.version = PHImageRequestOptionsVersionCurrent;
[[PHImageManager defaultManager] requestImageForAsset:self.lastAsset targetSize:CGSizeMake(42.0f, 42.0f) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->barButtonItem setImage:[result imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
});
}];
}
}
}
I put logs in the above functions to find where it crashes. And it crashes after the below code. I could not figure out the problem. Is there any restriction in the project setting to reach Camera Roll or is this problem related to thread?
self.fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
If the same crash happens also using UIImagePickerController there is most likely an issue with the iOS Photo Library on this one device. The iOS Photo Library is a Core-Data database under the hood and can be corrupt. Did you have a look at the crash report. If you see any SQL-/Core-Data issues this would point to database problems.
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.)
ALAssetsLibraryWriteVideoCompletionBlock is returning undefined values. If assetURL is equal to nil, then error ought to be not equal to nil, that is, return to me some error description.
See apple documentation at here.
When I record small video, ALAssetsLibraryWriteVideoCompletionBlock return a good value of assetURL, but when I record long video 3 or 4 Gb, assetURL return nil and error return nil. Video recorded is in tmp file because I can see this video in temporally folder in my app. It seems like IOS framework try to do a copy of this temporally file to photo album and iPhone don't have enough memory to copy this temp file to photo album and return a path of this file (assetURL).
Is this a bug in iOS framework? If so, is there a way to fix it?
UPDATE:
My files is less than 4GB. Thanks
UPDATE with source code:
-(void)recorder:(AVCamRecorder *)recorder recordingDidFinishToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error
{
if ([[self recorder] recordsAudio] && ![[self recorder] recordsVideo]) {
// If the file was created on a device that doesn't support video recording, it can't be saved to the assets
// library. Instead, save it in the app's Documents directory, whence it can be copied from the device via
// iTunes file sharing.
[self copyFileToDocuments:outputFileURL];
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
[[UIApplication sharedApplication] endBackgroundTask:[self backgroundRecordingID]];
}
if ([[self delegate] respondsToSelector:#selector(captureManagerRecordingFinished:)]) {
[[self delegate] captureManagerRecordingFinished:self];
}
}
else {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:outputFileURL
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
if ([[self delegate] respondsToSelector:#selector(captureManager:didFailWithError:)]) {
[[self delegate] captureManager:self didFailWithError:error];
}
}
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
[[UIApplication sharedApplication] endBackgroundTask:[self backgroundRecordingID]];
}
if (assetURL!=nil)
{
if ([[self delegate] respondsToSelector:#selector(captureManagerRecordingFinished:)]) {
[[self delegate] captureManagerRecordingFinished:self];
}
}
else
{
NSLog(#"Video is not saved");
NSString *alertMsg = [NSString stringWithFormat:#"Impossible to copy video to photo album"];
UIAlertView *alert = [[UIAlertView alloc] init];
[alert setTitle:#"info"];
[alert setMessage:alertMsg];
[alert setDelegate:self];
[alert addButtonWithTitle:#"Accept"];
[alert show];
}
}];
}
}
I have almost the same issue: just that I try to save photos, not movies; but still assetURL is NULL. Here is my code:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc]init];
[library writeImageToSavedPhotosAlbum:(__bridge CGImageRef)([info objectForKey:UIImagePickerControllerOriginalImage])
orientation:ALAssetOrientationUp completionBlock:^(NSURL *assetURL, NSError *error) {
if(error == nil) {
_myImageUrl = [NSString stringWithFormat:#"%#",assetURL];
NSLog(#"%#",assetURL);
} else NSLog(#"%#",error);
}];
[picker dismissViewControllerAnimated:YES completion:nil];
}
So, I guess this issue is not related to the size of the file..
I do different tests and I can save videos with more than 2Gb. In this case I delete a lot of files in my iPhone until I had 7Gb, then I saved a video with 3.2Gb (3.8Gb free) and store in photo gallery. Then I saved other video with 2Gb and it didn't save in photo gallery. What does it means?. Well, when you are saving a temp video, then you need at least the same or more free space than the video that you are trying to copy to photo gallery.
The problem here is that IOS7 framework doesn't return any error with his function writeVideoAtPathToSavedPhotosAlbum, in this case it have to return ALAssetsLibraryWriteDiskSpaceError = -3305. Maybe in previous version of IOS framework it works...but in IOS7 not.
By the way, I open a ticket in apple bug reporter. If they tell me something different I will post it.
Thanks for all guys.
ADD: FYI, I save a video file with more than 4Gb (4.6Gb) in IOS 7.0.3
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.
I am trying to get all the images that are in the photos.app and display them in a UICollectionView. I have this code to retrieve the images:
ALAssetsLibrary *al = [ViewController defaultAssetsLibrary];
ALAssetsGroupEnumerationResultsBlock groupEnumerAtion = ^(ALAsset *result, NSUInteger index, BOOL *stop){
if (result!=NULL) {
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
[imagesGotFromUserLibrary addObject:result];
}
}
};
ALAssetsLibraryGroupsEnumerationResultsBlock
libraryGroupsEnumeration = ^(ALAssetsGroup* group, BOOL* stop){
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group!=nil)
{
[group enumerateAssetsUsingBlock:groupEnumerAtion];
}
else
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
GalleryCollectionViewController *otherController = [[GalleryCollectionViewController alloc] init];
[otherController receiveImagesWithMutableArray:imagesGotFromUserLibrary];
});
}
};
al = [[ALAssetsLibrary alloc] init];
[al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:libraryGroupsEnumeration
failureBlock:^(NSError *error){
NSLog(#"ERROR: %#", error);
}];
This is in the viewDidLoad and then:
+ (ALAssetsLibrary *)defaultAssetsLibrary {
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
This piece of code is sending an array to another controller that will set the images to my UICollectionView. The problem is that I am getting an error "invalid attempt to access past the lifetime of its owning ALAssetsLibrary" and if I try to NSLog my array the result is something like "ALAsset - Type:Unknown, URLs:(null)".
I looked up on the internet and I found a solution. I should add this line of code but it doesn't work. The code is:
+ (ALAssetsLibrary *)defaultAssetsLibrary {
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
Anyone is able to help me on getting the correct images URLs to display?
The solution is to always use the same library for all accesses to the assets across all of your classes. The singleton solution you show above is good - so long as all of your classes call defaultAssetsLibrary and none of them alloc/init their own ALAssetsLibrary.
I have the same problem. And I finally solved it.
This is my condition: I have two controllers, the first one push to the second. In the first one I initialized library using following code:
let library = ALAssetsLibrary()
and I put all group information into an array, so that I can use them in the second view controller. But this may lead to no photo in group.
and the error message is the same as yours.
This is because the library is released by system. And all photo information is only valid in the lifetime of its owning ALAssetsLibrary. So the group.numberOfAssets() will be 0.
The solution is avoid library be released before the first controller release.
So I use the following code:
static let library = ALAssetsLibrary()
firstViewController.library.enmurate.....balabala
Hope this may help someone.