UIImagePickerController在iOS 8中无法呈现

56

有其他人在iOS 8上使用UIImagePickerController时遇到问题吗?以下方法在iPad的iOS 7上功能完好,但是当我尝试呈现选择器(最后一行)时,在XCode 6(Beta 3或4)中运行此方法会导致以下错误。如果有关系的话,sourceType的选择来自在同一位置呈现的alertView。

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

打开图片选择器的方法。

- (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];
            }
        }
    }
}

目前还没有;选择器确实显示出来了,但是我仍然在控制台中收到错误信息。 - Dave
在iOS 9及以上版本中,它可以正常工作。 - Radek Wilczak
12个回答

109

我认为这是因为在iOS 8中,警告视图和操作表实际上是被呈现的视图控制器 (UIAlertController)。所以,如果你正在响应UIAlertView的操作并呈现一个新的视图控制器,那么它会在UIAlertController被解除呈现时进行呈现。我通过延迟呈现UIImagePickerController直到下一个运行循环迭代来解决了这个问题,具体做法如下:

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

然而,正确的解决方法是在iOS 8上使用新的UIAlertController API(即使用if ([UIAlertController class]) ...进行测试)。如果您还无法使用新的API,则可以使用此解决方法。


2
修复此问题的正确方法是在iOS 8上使用新的UIActionController API(使用if ([UIActionController class]) ...进行测试)。如果您还不能使用新的API,则这只是一个解决方法。 - Ben Lings
@BenLings 应该是UIAlertController,而不是UIActionController吧? - Peter Heide
@PeterHeide - 你是对的,那应该是 UIAlertController - Ben Lings
1
这段代码在iPhone和iPad上都可以正常运行:dispatch_async(dispatch_get_main_queue()) { // 在此处编写代码 } - aryaxt
你也可以使用NSTimer,并将其安排为0.5秒的延迟,不要重复。在执行segue之前,需要关闭UIActionSheet视图。尝试通过以下方式调用新方法:[NSTimer scheduledTimerWithTimeInterval:.5f target:self selector:@selector(YOURMETHODHERE) userInfo:nil repeats:NO]; - DrRocker
显示剩余2条评论

80

我同意Ben Lings的问题检测。如果使用UIActionSheet,我建议一个更简单的解决方案。我只是将我的代码从操作表选择上的反应移动到了:

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

转化为:

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

这种方式可以保证应用程序在UIActionSheet动画完成后执行代码。

由于UIAlertView具有类似的委托方法:

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

我想类似的解决方案可能适用。


2
非常适合我的完美解决方案。谢谢。 - Jim Tierney
2
这是正确的答案,因为它在iOS7和iOS8下都能正常工作。非常感谢。 - Sobakus
2
这应该是正确的答案。因为NSOperationQueue可能随着新版本更新而发生变化,从而破坏当前选择的答案的工作。 - Drmorgan
我认为这也是正确的方式!但是我认为这是苹果永远无法解决的一个错误。这是我的情况:我有一个“分享”按钮,它显示一个带有两个按钮“压缩并复制到剪贴板”和另一个“打印”的UIActionSheet。制作zip文件需要一段时间,因此在隐藏动画正在进行时完成,这就是为什么我更喜欢clickedButtonAtIndex:。打印机交互控制器与此线程中的照片控制器具有相同的限制,因此它必须从didDismissWithButtonIndex:开始,这意味着我需要将我的逻辑分成两个方法?真是一团糟! - nacho4d
我更喜欢他们在一个方法中添加两个完成调用。其中一个将使您能够在用户单击按钮后尽快做出反应,另一个将使您能够继续使用相同的单击和操作结果更新UI。但这将破坏他们简单的API模式/事件。 - vedrano

12

这里是对我有效的解决方法

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];
}

记得分配cameraUI

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

建设并前往!


8
我在iOS 8上遇到了同样的问题。 然后我在设备上看到了最新更新iOS 8.0.2的更改日志。
在这个更新中提到:
“修复了一个阻止某些应用程序访问照片库的问题”
因此,请在使用XCode 6测试您的应用程序时,使用安装有iOS 8.0.2版本的设备进行测试,它将正常工作。 不要在iOS 8.0模拟器上进行测试。
这对我有所帮助,希望对你也有帮助。
屏幕截图如下:

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];
}

3

你可以通过使用以下代码来关闭当前打开的视图控制器:

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

这对我很有帮助。

这对我有用 - 在呈现新的视图控制器之前,只需插入此行即可。为什么没有更多的赞?有人遇到过这个问题吗? - IanS

2

您需要做的只是关闭已经呈现的ViewController:

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

[self openPhotoPicker:sourceType];

如果仍然出现错误,请将openPhotoPicker:放入完成处理程序中。

1

我经历了很多痛苦才想出一个适用于iPad和iPhone的解决方案,这是最终代码,其中一些来自其他人的评论:

代码有一些错误,但这是一个非常好的起点 :)

定义:

__weak IBOutlet UIButton *attachButton;
UIImage *image;

按钮的操作:

    - (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;

}

1
在iOS 8上,您应该使用新的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]
}

我建议你观看2014 WWDC session 228 a look in side presentation controllers,该视频与演示控制器相关。

1
最后一行应该是 [self presentViewController:self.imagePickerController animated:YES completion:nil];。虽然这很好知道,但旧的方法在iOS8上仍然有效,并且在我的经验中,重新实现新的API并不能解决这个特定的问题。 - Clafou
这并没有解决问题,正如Clafou所说,showViewController那行代码有误并导致了崩溃。 - w0mbat

1
我只是这样做了:

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];

    });

});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接