56

Is anyone else having an issue with UIImagePickerController in iOS 8? The method below works perfectly well in iOS 7 on an iPad, but I get the following error when I run this in XCode 6 (Beta 3 or 4) when I try to present the picker (last line). If it matters, the selection of the sourceType is coming from an alertView that is presented in the same place.

Warning: Attempt to present <UIImagePickerController: 0x7c0ae400>  on <CAGUCreateContactViewController: 0x7bf61a00> which is already presenting (null)

Method to open imagePicker.

- (void)openPhotoPicker:(UIImagePickerControllerSourceType)sourceType
{
    if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
        NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
        if ([availableMediaTypes containsObject:(NSString *)kUTTypeImage]) {
            UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
            imagePickerController.modalPresentationStyle = UIModalPresentationFullScreen;
            imagePickerController.sourceType = sourceType;
            imagePickerController.mediaTypes = @[(NSString *)kUTTypeImage];
            imagePickerController.delegate = self;

            self.imagePickerController = imagePickerController;

            if (sourceType == UIImagePickerControllerSourceTypeCamera) {
                [self presentViewController:self.imagePickerController animated:YES completion:nil];
            } else {                    
                if (self.popoverVC) {
                    [self.popoverVC dismissPopoverAnimated:YES];
                    self.popoverVC = nil;
                }

                self.popoverVC = [[UIPopoverController alloc] initWithContentViewController:imagePickerController];
                [self.popoverVC presentPopoverFromRect:self.nameAndPicCell.picture.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
            }
        }
    }
}
Eugen Martynov
  • 19,888
  • 10
  • 61
  • 114
Dave
  • 2,409
  • 3
  • 22
  • 28

12 Answers12

109

I think this is because in iOS 8, alert views and action sheets are actually presented view controllers (UIAlertController). So, if you're presenting a new view controller in response to an action from the UIAlertView, it's being presented while the UIAlertController is being dismissed. I worked around this by delaying the presentation of the UIImagePickerController until the next iteration of the runloop, by doing this:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [self openPhotoPicker:sourceType];
}];

However, the proper way to fix this is to use the new UIAlertController API on iOS 8 (i.e. use if ([UIAlertController class]) ... to test for it). This is just a workaround if you can't use the new API yet.

Ben Lings
  • 28,823
  • 13
  • 72
  • 81
  • 2
    The proper way to fix this is to use the new `UIActionController` API on iOS 8 (use `if ([UIActionController class]) ...` to test for it). This is just a workaround if you can't use the new API yet. – Ben Lings Sep 30 '14 at 14:38
  • @BenLings Shouldn't it be `UIAlertController`, not `UIActionController`? – Peter Heide Oct 10 '14 at 18:38
  • @PeterHeide - you're right, that should be be `UIAlertController`. – Ben Lings Oct 13 '14 at 08:21
  • Worked for me on iOS 8 simulator, will check on device. – Satheesh Jan 17 '15 at 12:11
  • 1
    This worked on both iphone and ipad ```dispatch_async(dispatch_get_main_queue()) { // Code here }``` – aryaxt Jun 05 '15 at 22:19
  • You can also use an NSTimer and schedule it with a .5f second delay with NO as repeat. The UIActionSheet view needs to be dismissed before you can perform segue. Try calling a new method by: `[NSTimer scheduledTimerWithTimeInterval:.5f target:self selector:@selector(YOURMETHODHERE) userInfo:nil repeats:NO];` – DrRocker Jul 10 '15 at 02:22
  • Your explanation is great, but why not just dismiss the previous action sheet first? (Like in Ritu's solution below) – IanS Dec 05 '16 at 16:28
80

I agree with Ben Lings issue detection. I would suggest a simpler solution in case when using UIActionSheet. I simply moved my code that reacts on Action Sheet selection from:

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex;
{
// my code
}

into:

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex;  // after animation
{
// my code
}

This way app is guarantied that code will be executed AFTER UIActionSheet animation finishes.

Since UIAlertView has similar delegate method:

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex;  // after animation
{
// my code
}

I suppose that similar solution may apply.

vedrano
  • 2,961
  • 1
  • 28
  • 25
  • 2
    This is the proper answer, as it works fine under both iOS7 and iOS8. Thanks a lot – Sobakus Dec 05 '14 at 12:05
  • 2
    This should be the correct answer. As NSOperationQueue could change at any time with new version updates and break the current selected answer from working. – Drmorgan Feb 10 '15 at 19:03
  • I think this is the correct way too!. However I consider this a bug Apple will never solve. This is my case: I have a "share" button that shows a `UIActionSheet` with two buttons "zip and copy to clipboard" and another "print". Making the zip file takes a while, so it is done while the hiding animation is taking place that is why I prefer `clickedButtonAtIndex:`. The printer interaction controller has the same restriction as the photo controller in this thread so It has to start from `didDismissWithButtonIndex:` this means I need to have my logic divided in two methods? what a mess! – nacho4d Apr 22 '15 at 01:55
  • I would be more happy that they added both completion calls in one method. One would enable you to react as soon as possible after user clicked button and other would enable you to continue updating UI with results of the same click and action. But that would disrupt their simple API pattern will/did events. – vedrano Apr 23 '15 at 07:13
12

Here is a solution that worked for me

if([[[UIDevice currentDevice] systemVersion] floatValue]>=8.0)
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        [self presentViewController:cameraUI animated:NO completion:nil];
    }];

}
else{

    [controller presentViewController:cameraUI animated:NO completion:nil];
}

Remember to alloc cameraUI

UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;

Build and Go!

Tunvir Rahman Tusher
  • 6,421
  • 2
  • 37
  • 32
8

I was facing the same problem in iOS 8. Then I saw the change log of the latest update to iOS i.e. 8.0.2 on the device.

It is mentioned in this update that_

"Fixes an issue that prevented some apps from accessing photos from Photo Library"

So test your app using XCode 6 on device with iOS 8.0.2 version it will work fine Don't test it on iOS 8.0 simulator.

This helped me, hope the same for you.

Screen-shot of iOS 8.0.2 Update change log

Piyush Mathur
  • 1,617
  • 16
  • 33
4
UIImagePickerController *imagePickerController= [[UIImagePickerController alloc] init];
[imagePickerController setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];

// image picker needs a delegate so we can respond to its messages
[imagePickerController setDelegate:self];
self.shouldCallViewWillAppear = NO;

if(IS_IOS8)
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // Place image picker on the screen
        [self presentViewController:imagePickerController animated:YES completion:nil];
    }];
}
else
{
    [self presentViewController:imagePickerController animated:YES completion:nil];
}
Ramaraj T
  • 5,184
  • 4
  • 35
  • 68
Nirav Gadhiya
  • 6,342
  • 2
  • 37
  • 76
3

You can dismiss the presented view controller (if any) by using

[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];

This worked for me.

m00am
  • 5,910
  • 11
  • 53
  • 69
Ritu
  • 84
  • 5
  • This worked for me - just inserted this line before presenting the new view controller. Why does this not have more up-votes? Has anyone had an issue with this solution? – IanS Dec 05 '16 at 16:27
2

All you need to do is dismiss already presented ViewController:

if (self.presentedViewController) {
    [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}

[self openPhotoPicker:sourceType];

If it still produces error, put openPhotoPicker: to completion handler

Nikolay Krasnov
  • 363
  • 1
  • 14
1

On iOS 8 you should use the new API:

if (SYSTEM_VERSION_IOS_8) {
    self.imagePickerController.modalPresentationStyle = UIModalPresentationPopover;
    UIPopoverPresentationController *popPC = self.imagePickerController.popoverPresentationController;
    popPC.barButtonItem = self.popoverItem;
    popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
    [self presentViewController:self.imagePickerController animated:YES completion:nil]
}

I recommend you watch the 2014 WWDC session 228 a look in side presentation controllers

Vito Ziv
  • 1,631
  • 2
  • 11
  • 13
  • 1
    The last line should be `[self presentViewController:self.imagePickerController animated:YES completion:nil];`. Also while this is great to know, the old way still works on iOS8 and reimplementing with the new API doesn't solve this particular issue in my experience. – Clafou Sep 11 '14 at 16:06
  • This does not fix the problem, and as Clafou says, the showViewController line is wrong and causes a crash. – w0mbat Oct 24 '14 at 16:48
1

I simply did this:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,
                                         (unsigned long)NULL), ^(void) {

    [self retractActivePopover];

    dispatch_async(dispatch_get_main_queue(), ^ {

        _activePopover=imagePickerPopover;

        UIBarButtonItem *callingButton = (UIBarButtonItem*) sender;

        [imagePickerPopover presentPopoverFromBarButtonItem:callingButton permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];

    });

});
Sebastian Dwornik
  • 2,526
  • 2
  • 34
  • 57
1

I went through a lot of pain coming up with a solution which works on both iPad and iPhone, this is the final code which some of it comes from comments of other people: the code has some bugs but it's a very good place to start :)

definitions :

__weak IBOutlet UIButton *attachButton;
UIImage *image;

button's action :

    - (IBAction)doAttach:(id)sender {
    UIActionSheet *action = [[UIActionSheet alloc] initWithTitle:@"Select image from" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"From library",@"From camera", nil] ;
    [action showInView:self.view];
  }



#pragma mark - ActionSheet delegates

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if( buttonIndex == 1 ) {
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        if(authStatus == AVAuthorizationStatusAuthorized)
        {
            NSLog(@"%@", @"You have camera access");
        }
        else if(authStatus == AVAuthorizationStatusDenied)
        {
            NSLog(@"%@", @"Denied camera access");

            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if(granted){
                    NSLog(@"Granted access to %@", AVMediaTypeVideo);
                } else {
                    [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
                    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@“no camera access“
                                                                                   message: @“if you need to use camera in this application go to settings -> appName -> and turn on camera.”
                                                                            preferredStyle:UIAlertControllerStyleAlert];

                    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@“ok” style:UIAlertActionStyleDefault
                                                                          handler:^(UIAlertAction * action) {
                                                                          }];
                    [alert addAction:defaultAction];

                    [self presentViewController:alert animated:YES completion:nil];


                    NSLog(@"Not granted access to %@", AVMediaTypeVideo);
                    return ;
                }
            }];
        }
        else if(authStatus == AVAuthorizationStatusRestricted)
        {
            [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@“no camera access“
                                                                                   message: @“if you need to use camera in this application go to settings -> appName -> and turn on camera.”
                                                                            preferredStyle:UIAlertControllerStyleAlert];

            UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@“ok” style:UIAlertActionStyleDefault
                                                                  handler:^(UIAlertAction * action) {
                                                                  }];
            [alert addAction:defaultAction];

            [self presentViewController:alert animated:YES completion:nil];


            NSLog(@"%@", @"Restricted, normally won't happen");
        }
        else if(authStatus == AVAuthorizationStatusNotDetermined)
        {
            NSLog(@"%@", @"Camera access not determined. Ask for permission.");

            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                if(granted){
                    NSLog(@"Granted access to %@", AVMediaTypeVideo);
                } else {
                    NSLog(@"Not granted access to %@", AVMediaTypeVideo);
                    return ;
                }
            }];
        }
        else
        {
            [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@“No camera access“
                                                                           message: @“error accusing camera”
                                                                    preferredStyle:UIAlertControllerStyleAlert];

            UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@“ok” style:UIAlertActionStyleDefault
                                                                  handler:^(UIAlertAction * action) {
                                                                  }];
            [alert addAction:defaultAction];

            [self presentViewController:alert animated:YES completion:nil];


            return;
            //NSLog(@"%@", @"Camera access unknown error.");
        }

        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {


            UIImagePickerController *pickerView =[[UIImagePickerController alloc]init];
            pickerView.allowsEditing = YES;
            pickerView.delegate = self;
            pickerView.sourceType = UIImagePickerControllerSourceTypeCamera;


            if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {

                [ self.presentedViewController dismissViewControllerAnimated:YES completion:nil ];

                pickerView.modalPresentationStyle = UIModalPresentationPopover;
                UIPopoverPresentationController *popPC = pickerView.popoverPresentationController;
                popPC.sourceView = attachButton;
                popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
                [self presentViewController:pickerView animated:YES completion:nil];
            } else {
                [self presentModalViewController:pickerView animated:YES ];
            }
        }

    }else if( buttonIndex == 0 ) {

        ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
        switch (status) {
            case ALAuthorizationStatusRestricted:
            case ALAuthorizationStatusDenied:
            {
                [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
                UIAlertController* alert = [UIAlertController alertControllerWithTitle:@“no access to library”
                                                                               message: @“if you wish to access photos in this app go to settings -> appName-> and turn on photos .”
                                                                        preferredStyle:UIAlertControllerStyleAlert];

                UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@“ok” style:UIAlertActionStyleDefault
                                                                      handler:^(UIAlertAction * action) {
                                                                      }];
                [alert addAction:defaultAction];

                [self presentViewController:alert animated:YES completion:nil];

            }
                break;

            default:
            {
                UIImagePickerController *pickerView = [[UIImagePickerController alloc] init];
                pickerView.allowsEditing = YES;
                pickerView.delegate = self;

                [pickerView setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];


                if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {

                    [ self.presentedViewController dismissViewControllerAnimated:YES completion:nil ];

                    pickerView.modalPresentationStyle = UIModalPresentationPopover;
                    UIPopoverPresentationController *popup = pickerView.popoverPresentationController;
                    popup.sourceView = attachButton;
                    popup.permittedArrowDirections = UIPopoverArrowDirectionAny;
                    [self presentViewController:pickerView animated:YES completion:nil];
                } else {
                    [self presentModalViewController:pickerView animated:YES ];
                }
            }
                break;
        }



    }
}

#pragma mark - PickerDelegates

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

    [self dismissModalViewControllerAnimated:true];

    UIImage * img = [info valueForKey:UIImagePickerControllerEditedImage];
    image = img;

}
Omid S.
  • 731
  • 7
  • 15
0

performSelector:withObject:afterDelay solved my problem.

also didDismissWithButtonIndex do the trick.

Max

masgar
  • 1,875
  • 2
  • 20
  • 32
  • Switching from `clickButtonAtIndex` to `didDismissWithButtonIndex` was the simplest and best solution for me. – duncanc4 Oct 26 '15 at 12:13
0

Here's a Xamarin solution. What worked for me was to add my actions to a Dismissed event handler.

this.btnPhoto.TouchUpInside += (sender, e) =>
{
    actionSheet = new UIActionSheet ("Add Photo");
    actionSheet.AddButton ("Take Photo");
    actionSheet.AddButton ("Select from Library");
    actionSheet.AddButton ("Cancel");
    actionSheet.DestructiveButtonIndex = -1; // red
    actionSheet.CancelButtonIndex = 3;  // black
    actionSheet.Clicked += delegate(object a, UIButtonEventArgs b)
    {
        actionSheet.Dismissed += (object aSender, UIButtonEventArgs dismissArgs) => 
        {
            switch (dismissArgs.ButtonIndex)
            {
                case 0:
                    showCamera ();
                    break;
                case 1:
                    showPhotoLibrary ();
                    break;
            }
        };
    };
    actionSheet.ShowInView (view);
};
Vincent
  • 155
  • 3
  • 11