64

I am using this code, but unfortunately it doesn't work.

After a user has denied camera access, I want to ask them for permission to use the camera again the next time they try to load it (in this case it's a barcode scanner using the camera view). I always get AVAuthorizationStatusDenied and then granted always automatically returns NO even though I ask for it again in code.

Many of my users are e-mailing me saying "my screen is black when I try to barcode scan" and it's because they have denied camera access for some reason. I want to be able to prompt them again because most likely the denial was a mistake.

Is there a possible way to do this?

    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 {
                NSLog(@"Not granted access to %@", AVMediaTypeVideo);
            }
        }];
    }
    else if(authStatus == AVAuthorizationStatusRestricted)
    {
        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);
            }
        }];
    }
    else
    {
        NSLog(@"%@", @"Camera access unknown error.");
    }
Ethan Allen
  • 14,425
  • 24
  • 101
  • 194
  • 4
    Unfortunately there is no way to ask again. You'll have to just pop up a UIAlertView and let them know to enable it in settings. One option to help cut down on people accidently hitting no might be to create a splash screen before you're about to ask and warn the user that if they don't hit allow they won't be able use the app properly. – TyloBedo Sep 27 '14 at 01:57

4 Answers4

97

After some research it looks like you can't do what I'd like. Here is the alternative that I coded to pop a dialog and open the Settings app automatically if on iOS 8+.

Some notes:

  • Since iOS 10 you need to specify NSCameraUsageDescription key in your Info.plist to be able ask for camera access, otherwise your app will crash at runtime.
  • Once the user changes any permissions for your app, it will kill your app. Handle accordingly and save any needed data before the user hits that "Go" button.
  • At some point between iOS 8 and 11, Apple no longer required the user to touch the Privacy cell in the Setting apps in order to get to and change the Camera settings. You may want to change your instructions on what the user is supposed to do in the Settings app based on what iOS version they are using. If someone wants to leave a comment below telling us all what exact iOS version that changed in, that would be awesome.
  • As of the last edit of this answer, the code below is working on iOS 14.2.

Swift 5.2:

At the top of your view controller:

import AVFoundation

Before opening the camera view:

@IBAction func goToCamera()
{
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    switch (status)
    {
    case .authorized:
        self.popCamera()

    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { (granted) in
            if (granted)
            {
                self.popCamera()
            }
            else
            {
                self.camDenied()
            }
        }

    case .denied:
        self.camDenied()

    case .restricted:
        let alert = UIAlertController(title: "Restricted",
                                      message: "You've been restricted from using the camera on this device. Without camera access this feature won't work. Please contact the device owner so they can give you access.",
                                      preferredStyle: .alert)

        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okAction)
        self.present(alert, animated: true, completion: nil)
    @unknown default:
        fatalError()
    }
}

Denial alert with completion block:

func camDenied()
{
    DispatchQueue.main.async
    {
        var alertText = "It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Close this app.\n\n2. Open the Settings app.\n\n3. Scroll to the bottom and select this app in the list.\n\n4. Turn the Camera on.\n\n5. Open this app and try again."

        var alertButton = "OK"
        var goAction = UIAlertAction(title: alertButton, style: .default, handler: nil)

        if UIApplication.shared.canOpenURL(URL(string: UIApplication.openSettingsURLString)!)
        {
            alertText = "It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Touch the Go button below to open the Settings app.\n\n2. Turn the Camera on.\n\n3. Open this app and try again."

            alertButton = "Go"

            goAction = UIAlertAction(title: alertButton, style: .default, handler: {(alert: UIAlertAction!) -> Void in
                UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
            })
        }

        let alert = UIAlertController(title: "Error", message: alertText, preferredStyle: .alert)
        alert.addAction(goAction)
        self.present(alert, animated: true, completion: nil)
    }
}

Objective-C:

At the top of your view controller:

#import <AVFoundation/AVFoundation.h>

Before opening the camera view:

- (IBAction)goToCamera
{
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if(authStatus == AVAuthorizationStatusAuthorized)
    {
        [self popCamera];
    }
    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);
                [self popCamera];
            }
            else
            {
                NSLog(@"Not granted access to %@", AVMediaTypeVideo);
                [self camDenied];
            }
        }];
    }
    else if (authStatus == AVAuthorizationStatusRestricted)
    {
        // My own Helper class is used here to pop a dialog in one simple line.
        [Helper popAlertMessageWithTitle:@"Error" alertText:@"You've been restricted from using the camera on this device. Without camera access this feature won't work. Please contact the device owner so they can give you access."];
    }
    else
    {
        [self camDenied];
    }
}

Denial alert:

- (void)camDenied
{
    NSLog(@"%@", @"Denied camera access");
    
    NSString *alertText;
    NSString *alertButton;
    
    BOOL canOpenSettings = (&UIApplicationOpenSettingsURLString != NULL);
    if (canOpenSettings)
    {
        alertText = @"It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Touch the Go button below to open the Settings app.\n\n2. Turn the Camera on.\n\n3. Open this app and try again.";
        
        alertButton = @"Go";
    }
    else
    {
        alertText = @"It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Close this app.\n\n2. Open the Settings app.\n\n3. Scroll to the bottom and select this app in the list.\n\n4. Turn the Camera on.\n\n5. Open this app and try again.";
        
        alertButton = @"OK";
    }
    
    UIAlertView *alert = [[UIAlertView alloc]
                          initWithTitle:@"Error"
                          message:alertText
                          delegate:self
                          cancelButtonTitle:alertButton
                          otherButtonTitles:nil];
    alert.tag = 3491832;
    [alert show];
}

Delegate call for the UIAlertView:

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (alertView.tag == 3491832)
    {
        BOOL canOpenSettings = (&UIApplicationOpenSettingsURLString != NULL);
        if (canOpenSettings)
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
    }
}
Ethan Allen
  • 14,425
  • 24
  • 101
  • 194
  • 1
    You forget to deal with the "Restricted" status. That's quite different from how "Denied" should be handled. – rmaddy Sep 27 '14 at 02:10
  • And your code to launch settings is incorrect. That launches your own app's settings page in the Settings app, not the general settings. – rmaddy Sep 27 '14 at 02:12
  • 3
    @maddy - How is restricted any different? And yes, I want it to go to my own app's Settings page... that is where you change the privacy settings to allow camera access. – Ethan Allen Sep 28 '14 at 01:36
  • Your app's settings bundle does not contain the privacy setting for the camera. That's on the Privacy | Camera page of the Settings app. And Restricted is different from Denied. Denied means the user chose to deny your app and they can run Settings and go to Privacy then Camera and turn the app on. Restricted means someone (like a parent) ran Settings and went to General then Restrictions. Then enabled Restrictions (with a passcode) and disabled the camera for all apps. The user of your app will never be given a chance to allow or deny camera access. – rmaddy Sep 28 '14 at 03:07
  • @rmaddy - Your comment about Restricted makes sense. BUT the code above I just tested works fine for jumping into the Settings app and into the section for my app. From there I just touch Privacy/Camera and I'm done. Test it yourself, it works just fine. – Ethan Allen Sep 28 '14 at 18:15
  • OK I have updated the code to do account for `AVAuthorizationStatusRestricted`. – Ethan Allen Sep 29 '14 at 19:56
  • Regarding AVAuthorizationStatusRestricted, I'm seeing something very unusual. If I go into Settings > General > Restrictions and disallow Camera access, at run-time in my app authorizationStatusForMediaType always returns "Denied" and never returns "Restricted". (This is true even when the app settings has the camera enable) This is problematic because in our app we'd like the give the user exact instructions in the popup for how to resolve the problem. The return value of "Denied" in the case where it is actually "Restricted" doesn't allow for this. Is anyone else observing this problem? – Chris Oct 02 '14 at 03:50
  • Interesting but unfortunate. I guess thats just the way it works. In Settings > General > Restrictions > Camera (app) set to "Off", means that 3rd party apps will receive the Denied return value. Note that the "Privacy" section in Settings > General > Restrictions does not have any Camera setting although it does have a Microphone setting. :( – Chris Oct 02 '14 at 04:11
  • 2
    @EthanAllen I stand corrected on the launch of the settings page. It's a new feature in iOS 8 that all apps now appear in the Settings app, not just apps with settings bundles. Apps without settings bundles show the privacy settings. – rmaddy Oct 04 '14 at 03:37
  • What about photo Library accessing? – user2526811 Sep 04 '15 at 11:57
  • I like this answer but I found a bit of a problem. When the user selects "Goto" and enables Camera after the return [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] still returns Denied status. Any suggestion from somebody please? – Teddy Dec 28 '15 at 18:34
  • Where is popcamera method – Anand Prakash Jan 07 '16 at 09:12
  • @anand - `[self popCamera];` is just a method that shows the camera. You can figure that one on your own as it's not really relevant to this question/answer. It's pretty easy. – Ethan Allen Feb 09 '16 at 21:39
  • I have same issue as Teddy, after changing value in Settings it is not reflected afterwards in the app. To get new value you need to restart the app. Any workaround for that? – Balki Mar 24 '16 at 13:49
  • requestAccessForMediaType: the callback can happen on any queue. You need to pop the camera on the UI thread to avoid unfortunate side effects. – Graham Perks Mar 30 '16 at 15:21
11

Once they have denied camera access, the user can authorize camera use for your app in Settings. By design, you can't override this in your own code.

You can detect this case with the following sample code and then explain to the user how to fix it: iOS 7 UIImagePickerController Camera No Image

NSString *mediaType = AVMediaTypeVideo; // Or AVMediaTypeAudio

AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];

// The user has explicitly denied permission for media capture.
else if(authStatus == AVAuthorizationStatusDenied){
    NSLog(@"Denied");
}
Community
  • 1
  • 1
StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
3

For Swift 3.0

This will lead the user to settings for changing the permission.

func checkCameraAuthorise() -> Bool {
    let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
    if status == .restricted || status == .denied {
        let dialog = ZAlertView(title: "", message: "Please allow access to the camera in the device's Settings -> Privacy -> Camera", isOkButtonLeft: false, okButtonText: "OK", cancelButtonText: "Cancel", okButtonHandler:
            { _ -> Void in UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)}, cancelButtonHandler: { alertView in alertView.dismissAlertView() })
        dialog.show()
        return false
    }
    return true
}
ak_ninan
  • 721
  • 7
  • 15
1

Full code of Camera access and photo library access

import AVFoundation

To handle camera action use the below code: Method calling

func openCameraOrLibrary(){
    let imagePicker = UIImagePickerController()
    let alertController : UIAlertController = UIAlertController(title: "Select Camera or Photo Library".localized, message: "", preferredStyle: .actionSheet)
    let cameraAction : UIAlertAction = UIAlertAction(title: "Camera".localized, style: .default, handler: {(cameraAction) in

        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) == true {
            if self.isCamAccessDenied() == false { **//Calling cam access method here**
                imagePicker.sourceType = .camera
                imagePicker.delegate = self
                self.present(imagePicker, animated: true, completion: nil)
            }

        }else{
            self.present(self.showAlert(Title: "", Message: "Camera is not available on this Device or accesibility has been revoked!".localized), animated: true, completion: nil)
            self.showTabbar()

        }

    })

    let libraryAction : UIAlertAction = UIAlertAction(title: "Photo Library", style: .default, handler: {(libraryAction) in

        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.photoLibrary) == true {

            imagePicker.sourceType = .photoLibrary
            imagePicker.delegate = self
            self.present(imagePicker, animated: true, completion: nil)

        }else{
            self.showTabbar()
            self.present(self.showAlert(Title: "", Message: "Photo Library is not available on this Device or accesibility has been revoked!".localized), animated: true, completion: nil)
        }
    })

    let cancelAction : UIAlertAction = UIAlertAction(title: "Cancel".localized, style: .cancel , handler: {(cancelActn) in
        self.showTabbar()
    })

    alertController.addAction(cameraAction)

    alertController.addAction(libraryAction)

    alertController.addAction(cancelAction)

    alertController.popoverPresentationController?.sourceView = view
    alertController.popoverPresentationController?.sourceRect = view.frame

    self.present(alertController, animated: true, completion: nil)
    self.hideTabbar()

}

Method to handle camera access functionality

func isCamAccessDenied()-> Bool
{
    let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
    if status == .restricted || status == .denied {
    DispatchQueue.main.async
        {
            var alertText = "It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Close this app.\n\n2. Open the Settings app.\n\n3. Scroll to the bottom and select this app in the list.\n\n4. Turn the Camera on.\n\n5. Open this app and try again."

            var alertButton = "OK"
            var goAction = UIAlertAction(title: alertButton, style: .default, handler: nil)

            if UIApplication.shared.canOpenURL(URL(string: UIApplicationOpenSettingsURLString)!)
            {
                alertText = "It looks like your privacy settings are preventing us from accessing your camera to do barcode scanning. You can fix this by doing the following:\n\n1. Touch the Go button below to open the Settings app.\n\n2. Turn the Camera on.\n\n3. Open this app and try again."

                alertButton = "OK"

                goAction = UIAlertAction(title: alertButton, style: .default, handler: {(alert: UIAlertAction!) -> Void in
                    UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: nil)
                })
            }

            let alert = UIAlertController(title: "Error", message: alertText, preferredStyle: .alert)
            alert.addAction(goAction)
            self.present(alert, animated: true, completion: nil)
        }
        return true
    }
    return false
}
Alok
  • 24,880
  • 6
  • 40
  • 67