Issue with ELC Image Picker when too many images are selected iPhone - objective-c

I am using ELC Image Picker in my project. Here i am getting one issue that is:
when i selected images like 20 picker is working fine but when I select images like 32(selected images count) my app is crashing before dismissal of controller itself and I am getting the error:
Program received signal: “0”. Data Formatters temporarily
unavailable, will re-try after a 'continue'. (Unknown error loading
shared library "/Developer/usr/lib/libXcodeDebuggerSupport.dylib")
And also I am getting:
Received memory warning. Level=1
NOTE: when this situation is happened is, first i selected 32 images worked fine and again I selected same number of images it was crashing.
Also I've tried with the example: github ELCImagePickerController project.
Can any one give me the answer to over come this?

From error you can see that its a memory issue
So you have 2 options
set a limit for number of images can be choosed
in background save images to temp folder
OR
Customize ELC picker code so that...when a person selects an image... it will take only image path but not image content
and when they are done... now run a loop to get those images into your app.

#SteveGear following code will solve your problem. Just provide the UIImagePickerControllerReferenceURL and you will get the NSData. Its long time but still, it may help others.
ALAssetsLibrary *assetLibrary=[[ALAssetsLibrary alloc] init];
NSURL *assetURL = [infoObject objectForKey:UIImagePickerControllerReferenceURL];
__block NSData *assetData;
[assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) // substitute assetURL with your url
{
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc((long)rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:(NSUInteger)rep.size error:nil];
assetData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];//this is NSData what you need.
//[data writeToFile:assetData atomically:YES]; //Uncomment this if you want to store the data as file.
}
failureBlock:^(NSError *err) {
NSLog(#"Error: %#",[err localizedDescription]);
}];
Here assetData is what you need.

Related

UIDocumentInteractionController weird behaviour on iphone with iOS7

I am hitting the wall with a problem for two days now and i would like your help. Before i start i should say that this problem is on iPhone 5 with iOS7 (I have also tested on iPhone 4 with iOS 6 and iPad 2 with iOS 7). This problem began when i tried to upgrade an application that has been on AppStore (iOS4 initially) and tried to make it iOS 7 compatible (supported on iOS6 onwards).
The scenario is pretty simple. I have a view with is a UIDocumentInteractionControllerDelegate. I download a file from a web service save it on the NSTemporaryDirectory and allow the user to Preview or Open In another app using the presentOptionsMenuFromRect. The code simplified is as follow:
I have declared a #property (nonatomic, strong) UIDocumentInteractionController *docController;
#autoreleasepool {
NSString *fileName = "uniquefilename"
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
fileURL = [NSURL fileURLWithPath:filePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:filePath]){
NSData *fileData = [NSData dataWithContentsOfURL:[NSURL URLWithString:"theurlofthefile"]];
NSError *writeError = nil;
[fileData writeToURL: fileURL options:0 error:&writeError];
if(writeError) {
//show error
}
}
docController = [UIDocumentInteractionController interactionControllerWithURL:url];
docController.delegate = self;
if (isIpad) {
[docController presentOptionsMenuFromRect:CGRectMake(location.x + 400,location.y, 100, 100) inView:tableView animated:YES];
}
else{
[docController presentOptionsMenuFromRect:CGRectZero inView:self.tabBarController.view animated:YES];
}
}
The problem is that i receive all kind of errors, i repeat the same process all the time and i get different errors, sometimes it works for many times in a row, sometimes it fails from the first go. The errors i receive amongst others which i will add when i get them again are:
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSSetM: 0x16ff61e0> was mutated while
being enumerated.'
malloc: * error for object 0x177a56a4: incorrect checksum for
freed object - object was probably modified after being freed.
malloc: * error for object 0x1c2c8b50: double free * set a
breakpoint in malloc_error_break to debug
When the OptionsMenu is showed successfully i see "AirDrop: collectionView:layout:insetForSectionAtIndex:, orientation: 1, sectionInset: {0, 10, 0, 10}" in the console.
I tried enabling NSZombies and putting a breakpoint for malloc error but did not help me in any way.
Please help me or guide me to the right direction.
Thank you.
Bit late to answer, but hopefully it will help other people.
I had exactly the same issue when saving a file and presenting a UIDocumentInteractionController, it was absolutely random sometimes it would would perfectly 10 times in a row and sometimes it would crash on first try.
It seems to be caused by the file not having finished writing to disk, what fixed it for me was adding a delay before presenting the UIDocumentInteractionController to ensure that the file has finished writing to disk

Save multiple images quickly in iOS 6 (custom album)

I'm writing an application that will take several images from URL's, turn them into a UIImage and then add them to the photo library and then to the custom album. I don't believe its possible to add them to a custom album without having them in the Camera Roll, so I'm accepting it as impossible (but it would be ideal if this is possible).
My problem is that I'm using the code from this site and it does work, but once it's dealing with larger photos it returns a few as 'Write Busy'. I have successfully got them all to save if I copy the function inside its own completion code and then again inside the next one and so on until 6 (the most I saw it take was 3-4 but I don't know the size of the images and I could get some really big ones) - this has lead to the problem that they weren't all included in the custom album as they error'd at this stage too and there was no block in place to get it to repeat.
I understand that the actual image saving is moved to a background thread (although I don't specifically set this) as my code returns as all done before errors start appearing, but ideally I need to queue up images to be saved on a single background thread so they happen synchronously but do not freeze the UI.
My code looks like this:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:singleImage]]];
[self.library saveImage:image toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Error");
}
}];
I've removed the repetition of the code otherwise the code sample would be very long! It was previously where the NSLog code existed.
For my test sample I am dealing with 25 images, but this could easily be 200 or so, and could be very high resolution, so I need something that's able to reliably do this over and over again without missing several images.
thanks
Rob
I've managed to make it work by stripping out the save image code and moving it into its own function which calls itself recursively on an array on objects, if it fails it re-parses the same image back into the function until it works successfully and will display 'Done' when complete. Because I'm using the completedBlock: from the function to complete the loop, its only running one file save per run.
This is the code I used recursively:
- (void)saveImage {
if(self.thisImage)
{
[self.library saveImage:self.thisImage toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
[self saveImage];
}
else
{
[self.imageData removeObject:self.singleImageData];
NSLog(#"Success!");
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
}
}];
}
else
{
self.singleImageData = nil;
self.thisImage = nil;
self.imageData = nil;
self.images = nil;
NSLog(#"Done!");
}
}
To set this up, I originally used an array of UIImages's but this used a lot of memory and was very slow (I was testing up to 400 photos). I found a much better way to do it was to store an NSMutableArray of URL's as NSString's and then perform the NSData GET within the function.
The following code is what sets up the NSMutableArray with data and then calls the function. It also sets the first UIImage into memory and stores it under self.thisImage:
NSEnumerator *e = [allDataArray objectEnumerator];
NSDictionary *object;
while (object = [e nextObject]) {
NSArray *imagesArray = [object objectForKey:#"images"];
NSString *singleImage = [[imagesArray objectAtIndex:0] objectForKey:#"source"];
[self.imageData addObject:singleImage];
}
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
This means the rest of the getters for UIImage can be contained in the function and the single instance of UIImage can be monitored. I also log the raw URL into self.singleImageData so that I can remove the correct elements from the array to stop duplication.
These are the variables I used:
self.images = [[NSMutableArray alloc] init];
self.thisImage = [[UIImage alloc] init];
self.imageData = [[NSMutableArray alloc] init];
self.singleImageData = [[NSString alloc] init];
This answer should work for anyone using http://www.touch-code-magazine.com/ios5-saving-photos-in-custom-photo-album-category-for-download/ for iOS 6 (tested on iOS 6.1) and should result in all pictures being saved correctly and without errors.
If saveImage:toAlbum:withCompletionBlock it's using dispatch_async i fear that for i/o operations too many threads are spawned: each write task you trigger is blocked by the previous one (bacause is still doing I/O on the same queue), so gcd will create a new thread (usually dispatch_async on the global_queue is optimized by gcd by using an optimized number of threads).
You should either use semaphores to limit the write operation to a fixed number at the same time or use dispatch_io_ functions that are available from iOS 5 if i'm not mistaken.
There are plenty example on how to do this with both methods.
some on the fly code for giving an idea:
dispatch_semaphore_t aSemaphore = dispatch_semaphore_create(4);
dispatch_queue_t ioQueue = dispatch_queue_create("com.customqueue", NULL);
// dispatch the following block to the ioQueue
// ( for loop with all images )
dispatch_semaphore_wait(aSemaphore , DISPATCH_TIME_FOREVER);
[self.library saveImage:image
toAlbum:#"Test Album"
withCompletionBlock:^(NSError *error){
dispatch_semaphore_signal(aSemaphore);
}];
so every time you will have maximum 4 saveImage:toAlbum, as soon as one completes another one will start.
you have to create a custom queue, like above (the ioQueue) where to dispatch the code that does the for loop on the images, so when the semaphore is waiting the main thread is not blocked.

Mac OS App crashes when saving too many new NSManagedObjects

I'm trying to write an app that enumerates through folders and items which are dragged onto the main app window and then places a new entry for each PDF it finds into my Core Data database which in turn populates an NSTableView. To cut a long story short[er], I've got to a stage where I can safely drag a few items into the window and the database gets saved, allowing me to restart the app time after time.
The problem I have is that if I were to [say] drag my Documents folder onto the window it all appears to work but when I quit and attempt to restart then it crashes out with a
Thread 1: EXC_BAD_ACCESS (code=2, address-0x7fff6f11dff8
The code I'm currently using (specifically written for debugging this matter) is:
NSError *error;
id firstObject = [draggedStuff objectAtIndex:0];
NSDictionary *fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:firstObject error:&error];
if ([[fileAttribs valueForKey:NSFileType] isEqualTo:NSFileTypeDirectory]) {
NSManagedObject *aNewFolder = [[NSManagedObject alloc]initWithEntity:[NSEntityDescription entityForName:kFOLDERS_ENTITY inManagedObjectContext:[self managedObjectContext]] insertIntoManagedObjectContext:[self managedObjectContext]];
[aNewFolder setValue:firstObject forKey:kFOLDER_PATH];
NSArray *directoryContents = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:firstObject error:&error];
assert(!error);
[directoryContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CFStringRef fileExtension = (__bridge CFStringRef) [obj pathExtension];
CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL);
if (UTTypeConformsTo(fileUTI, kUTTypePDF)) {
NSManagedObject *aDocument = [[NSManagedObject alloc]initWithEntity [NSEntityDescription entityForName:kDOCO_ENTITY inManagedObjectContext:[self managedObjectContext]] insertIntoManagedObjectContext:[self managedObjectContext]];
[aNewPicture setValue:obj forKey:#"docoPath"];
}
CFRelease(fileUTI);
if (idx % 20 == 0) {
NSError *error;
[[self managedObjectContext] save:&error];
assert(!error);
}
}];
}
Initially, I had this in an NSOperation subclass with the relevent checking for user cancellation but I've moved it to the main thread just to make sure that it's not something to do with that, so, yes, it is currently not a user-friendly piece of code.
The crashing is definitely connected to the number of items that directoryContents returns with as it works absolutely fine if I drag a folder with only a matter of 20 or so items inside it. If directoryContents holds a matter of around 200 files or more then Core Data doesn't seem to save it correctly and corrupts the storedata file which needs to be Trashed before I can restart the app.
The if (idx % 20 == 0)... can be changed to save with more or less NSManagedObjects waiting to be saved but with the same results as well.
I've also tried using an NSDirectoryEnumerator with the same results: the corruption is always connected with the number of items in the folder.
I like Core Data, but sometimes I lose my way so any help is much appreciated especially given the length of this post!
Todd.
Turns out that it was much simpler than I thought: I'd managed to wire up my NSTable incorrectly.
I'd connected both the Table View and the Table column in the NIB to the entity.... Oops.

Correct way to multithread in objective-c?

I have a UITableView which displays images. Every cell has an image and every time a cell loads, I call a selector (from the cellForRowAtIndexPath) in the background like this:
[self performSelectorInBackground:#selector(lazyLoad:) withObject:aArrayOfData];
The only problem is that sometimes I get a crash (because I am changing data in the background while it's trying to be read elsewhere). Here's the error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0xce1a920> was mutated while being enumerated.'
When updating the data in the background, should I move it to the main selector and change it? Or should I call the #selector() differently?
Thanks!
If you can leave the operation on the main thread and have no lagginess nor problems you are done.
However: Let's assume you've already done that and encounter problems. The answer is: don't modify the array in the lazy load. Switch to the main thread to modify the array. See Brad's answer here:
https://stackoverflow.com/a/8186206/8047
for a way to do it with blocks, so you can send your objects over to the main queue (you should probably also use GCD for the call to the lazy load in the first place, but it's not necessary).
You can use #synchronized blocks to keep the threads from walking over each other. If you do
#synchronized(array)
{
id item = [array objectAtIndex:row];
}
in the main thread and
#synchronized(array)
{
[array addObject:item];
}
in the background, you're guaranteed they won't happen at the same time. (Hopefully you can extrapolate from that to your code—I'm not sure what all you're doing with the array there..)
It seems, though, like you'd have to notify the main thread anyway that you've loaded the data for a cell (via performSelectorOnMainThread:withObject:waitUntilDone:, say), so why not pass the data along, too?
Given the term 'lazy load' I am assuming that means you are pulling your images down from a server. (If the images are local then there is really no need for multithreading).
If you are downloading images off a server I would suggest using something along these lines (using ASIHTTPRequest)
static NSCache *cellCache; //Create a Static cache
if (!cellCache)//If the cache is not initialized initialize it
{
cellCache = [[NSCache alloc] init];
}
NSString *key = imageURL;
//Look in the cache for image matching this url
NSData *imageData = [cellCache objectForKey:key];
if (!imageData)
{
//Set a default image while it's loading
cell.icon.image = [UIImage imageNamed:#"defaultImage.png"];'
//Create an async request to the server to get the image
__unsafe_unretained ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
//This code will run when the request finishes
[request setCompletionBlock:^{
//Put downloaded image into the cache
[cellCache setObject:[request responseData] forKey:key];
//Display image
cell.icon.image = [UIImage imageWithData:[request responseData]];
}];
[request startAsynchronous];
}
else
{
//Image was found in the cache no need to redownload
cell.icon.image = [UIImage imageWithData:imageData];
}

AVAsset has no tracks or duration when created from an ALAsset URL

I'm pulling all of the video assets from ALAssetsLibrary (Basically everything that's being recorded from the native camera app). I am then running an enumeration on each video asset that does this to each video:
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
//Get the URL location of the video
ALAssetRepresentation *representation = [alAsset defaultRepresentation];
NSURL *url = [representation url];
//Create an AVAsset from the given URL
NSDictionary *asset_options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:url options:asset_options];//[AVURLAsset URLAssetWithURL:url options:asset_options];
//Here is the problem
NSLog([NSString stringWithFormat:#"%i", [avAsset.tracks count]]);
NSLog([NSString stringWithFormat:#"%f", CMTimeGetSeconds(avAsset.duration)]);
}
NSLog is reporting that the AVAsset that I've gotten from my ALAsset has 0 tracks, and has a duration of 0.0 seconds. I checked the url, and it's "assets-library://asset/asset.MOV?id=9F482CF8-B4F6-40C2-A687-0D05F5F25529&ext=MOV" which seems correct. I know alAsset is actually a video, and the correct video, because I've displayed alAsset.thumbnail, and it's shown the correct thumbnail for the video.
All this leads me to believe there's something going wrong in the initialization for avAsset, but for the life of me, I can't figure out what's going wrong. Can anyone help me?
Update:
I think i've confirmed that the url being given to me by ALAssetRepresentation is faulty, which is weird because it gives me the correct thumbnail. I added this code in:
NSLog([NSString stringWithFormat:#"%i", [url checkResourceIsReachableAndReturnError:&error]]);
NSLog([NSString stringWithFormat:#"%#", error]);
It gives me this:
0
Error Domain=NSCocoaErrorDomain Code=4 "The operation couldn’t be completed. (Cocoa error 4.)" UserInfo=0x19df60 {}
I'm still not sure what would cause that. The only thing I'm noticing is the url, which is "assets-library://asset/asset.MOV?id=9F482CF8-B4F6-40C2-A687-0D05F5F25529&ext=MOV" is different from what I've seen as I've been searching around for this. The one i've seen elsewhere looks more like "assets-library://asset/asset.MOV?id=1000000394&ext=MOV", with a number instead of an alphanumeric, dash separated name.
If it helps, I'm using XCode 4.2 Beta, and iOS5. Please let me know if you can think of anything. Thanks.
Okay, looks like it was a bug in the iOS5 beta v1. I upgraded to the newest and it worked. Thanks to those who took a look at my question.