Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Long delay when saving image taken with camera or chosen from camera roll - iPhone

I am using the following code to allow a user of my application to take/choose a photo, which will then be saved to the documents directory and set as the image of a UIImageView:

    -(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {

        if (actionSheet.tag == 0){
            if (buttonIndex == 0) {
                NSLog(@"Take Picture Button Clicked");
                // Create image picker controller
                UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

                // Set source to the camera
                imagePicker.sourceType =  UIImagePickerControllerSourceTypeCamera;

                // Delegate is self
                imagePicker.delegate = self;

                // Show image picker
                [self presentModalViewController:imagePicker animated:YES];
            } 
            else if (buttonIndex == 1) {
                NSLog(@"Choose From Library Button Clicked");
                // Create image picker controller
                UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

                // Set source to the camera
                imagePicker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;

                // Delegate is self
                imagePicker.delegate = self;

                // Show image picker
                [self presentModalViewController:imagePicker animated:YES];
            } 
            else if (buttonIndex == 2) {
                NSLog(@"Cancel Button Clicked");
            } 
        }
......

- (void)saveImage:(UIImage*)image:(NSString*)imageName 
{ 
    NSData *imageData = UIImagePNGRepresentation(image); //convert image into .png format.

    NSFileManager *fileManager = [NSFileManager defaultManager];//create instance of NSFileManager

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //create an array and store result of our search for the documents directory in it

    NSString *documentsDirectory = [paths objectAtIndex:0]; //create NSString object, that holds our exact path to the documents directory

    NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", imageName]]; //add our image to the path 

    [fileManager createFileAtPath:fullPath contents:imageData attributes:nil]; //finally save the path (image)

    receiptImageView1.image = [UIImage imageWithContentsOfFile:fullPath];
    self.receiptImage1 = fullPath;

    NSLog(@"image saved");
}

//Receive the image the user picks from the image picker controller
-(void)imagePickerController:(UIImagePickerController*)picker
didFinishPickingMediaWithInfo:(NSDictionary*)info {
    UIImage* image = [info objectForKey: UIImagePickerControllerOriginalImage];
    NSString* imageName = @"Receipt1Image1";
    [self saveImage:image :imageName];
}

Basically my problem is that this code seems to be executing very slowly, for example when I select an image from the camera roll it does eventually save and bring me back to the calling view, but only after a long delay..

Can anyone shed any light on this?

like image 952
Jack Nutkins Avatar asked Dec 26 '22 23:12

Jack Nutkins


2 Answers

Saving large images (like the ones taken by the camera in an iPhone 4/4S) takes a long time. If you profile the process, you'll find that UIImagePNGRepresentation() takes a while to generate your PNG image, but the primary bottleneck in my experience has been the writing to disk of a 1+ MB image.

There's little you can do to speed up this process aside from using JPEG compression, which I've found to be a little faster in my benchmarks, or using a faster third-party image compression routine. Therefore, if you don't want to block your user interface while this is happening, dispatch this saving process on a background thread or queue. You could do something like the following:

-(void)imagePickerController:(UIImagePickerController*)picker
didFinishPickingMediaWithInfo:(NSDictionary*)info {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage* image = [info objectForKey: UIImagePickerControllerOriginalImage];
        NSString* imageName = @"Receipt1Image1";
        [self saveImage:image :imageName];
    });
}

However, watch out for the fact that each of these UIImages use up quite a bit of memory to keep around, so you may want to use a dispatch semaphore to prevent more than one such image saving operation from occurring at the same time.

Also, as a stylistic note, defining an Objective-C method like

- (void)saveImage:(UIImage*)image:(NSString*)imageName 

while allowed, is highly discouraged. Give each parameter a name, like in the following:

- (void)saveImage:(UIImage*)image fileName:(NSString*)imageName 

It will make your code a lot more descriptive.

like image 171
Brad Larson Avatar answered Dec 29 '22 11:12

Brad Larson


I've answered a similar question. For clarity I will copy it here:

Depending on the image resolution, UIImagePNGRepresentation can indeed be quite slow, as can any writing to the file system.

You should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.

Newer versions of iOS make saving asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:

  1. Create an NSBlockOperation which saves the image
  2. In the block operation's completion block, read the image from disk & display it. The only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread.
  3. Add the block operation to an operation queue and watch it go!

That's it. And here's the code:

// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{

   [UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
   [UIImagePNGRepresentation(thumbImage) writeToFile:thumbfile atomically:YES];

}];

// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{

   [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 

      UIImage *image = [UIImage imageWithContentsOfFile:thumbfile];
      // TODO: Assign image to imageview

   }];
}];

// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];

Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.

like image 43
memmons Avatar answered Dec 29 '22 13:12

memmons