74

When my app tries to access the camera for the first time on iOS 8, the user is presented with a camera permission dialog, much like the microphone one for microphone access in iOS 7.

In iOS 7, it was possible to invoke the microphone permission dialog beforehand and see if the permission was granted (see this question, for example). Is there a similar way to invoke the camera permission dialog in iOS 8? Can the dialog be combined for microphone AND camera access permission?

Cœur
  • 37,241
  • 25
  • 195
  • 267
jamix
  • 5,484
  • 5
  • 26
  • 35
  • Just posted an answer that checks both camera & microphone access and catches the scenario where camera permissions are granted but microphone permissions are not. – Crashalot Jul 07 '15 at 08:29

9 Answers9

91

Here is the approach we ended up using:

if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType: completionHandler:)]) {
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
        // Will get here on both iOS 7 & 8 even though camera permissions weren't required 
        // until iOS 8. So for iOS 7 permission will always be granted.
        if (granted) {
            // Permission has been granted. Use dispatch_async for any UI updating
            // code because this block may be executed in a thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                [self doStuff];
            });                
        } else {
            // Permission has been denied.
        }
    }];
} else {
    // We are on iOS <= 6. Just do what we need to do.
    [self doStuff];
}
Stunner
  • 12,025
  • 12
  • 86
  • 145
jamix
  • 5,484
  • 5
  • 26
  • 35
  • 4
    A minor comment - The requestAccessForMediaType method is present in iOS 7 as well (asking for camera permission was required by iOS only in some regions back then). so the else part applies to < iOS 6. – Niraj Oct 31 '14 at 18:27
  • 2
    Updated the code comments to be a little more informative/correct after some testing of my own. – Stunner Jan 05 '15 at 10:11
  • In our tests, this code doesn't catch the scenario where camera permissions are granted but microphone permissions are denied. – Crashalot Jul 07 '15 at 08:08
  • 2
    This code is for camera permissions only, which is the subject of the original question. – jamix Jul 07 '15 at 11:10
  • 2
    For iOS 10+ Don't forget to put the NSCameraUsageDescription in your plist-- along with the purpose of asking for the permission. It will crash if you don't do this. – Jordan Hochstetler Mar 02 '17 at 19:32
62

I'm running into a similar issue, if the user has denied camera access when they are first prompted, pressing the button to take snapshot results in a black screen in the camera mode.

However I want to detect that the user has declined access and prompt them it must be turned on but I can't find any functions to check the current user camera access, is there such a function?

EDIT: The following check will let you know in IOS 8 about camera access:

#import <AVFoundation/AVFoundation.h>

AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    
    if(status == AVAuthorizationStatusAuthorized) { // authorized
        
    }
    else if(status == AVAuthorizationStatusDenied){ // denied
        
    }
    else if(status == AVAuthorizationStatusRestricted){ // restricted
        
        
    }
    else if(status == AVAuthorizationStatusNotDetermined){ // not determined
        
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if(granted){ // Access has been granted ..do something
               
            } else { // Access denied ..do something
               
            }
        }];
    }

This information was found on the following question (How to know that application have camera access or not programmatically in iOS8):

SmokersCough
  • 967
  • 7
  • 22
54

Here is my Swift Solution (iOS 8), I needed the camera for QR scanning so really had to prompt its use.

This provides

  1. Encourage the user to select allow if prior to the default allow camera access question

  2. Easy way to access settings if the user denied the first request.

To get it running call check camera in ViewDidAppear / or ViewDidLoad etc. I needed to use viewDidAppear so my custom camera views constraints were set up.

func checkCamera() {
    let authStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
    switch authStatus {
    case .authorized: break // Do your stuff here i.e. allowScanning()
    case .denied: alertToEncourageCameraAccessInitially()
    case .notDetermined: alertPromptToAllowCameraAccessViaSetting()
    default: alertToEncourageCameraAccessInitially()
    }
}

func alertToEncourageCameraAccessInitially() {
    let alert = UIAlertController(
        title: "IMPORTANT",
        message: "Camera access required for QR Scanning",
        preferredStyle: UIAlertControllerStyle.alert
    )
    alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
    alert.addAction(UIAlertAction(title: "Allow Camera", style: .cancel, handler: { (alert) -> Void in
        UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
    }))
    present(alert, animated: true, completion: nil)
}

func alertPromptToAllowCameraAccessViaSetting() {

    let alert = UIAlertController(
        title: "IMPORTANT",
        message: "Please allow camera access for QR Scanning",
        preferredStyle: UIAlertControllerStyle.alert
    )
    alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel) { alert in
        if AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo).count > 0 {
            AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { granted in
                DispatchQueue.main.async() {
                    self.checkCamera() } }
        }
        }
    )
    present(alert, animated: true, completion: nil)
}

Thanks to jamix above for the tip for using dispatch_async - makes the response to show the newly set camera function so much faster.

Sorry for a mix of trailing closures.. wanted to try them out.

Lena Schimmel
  • 7,203
  • 5
  • 43
  • 58
DogCoffee
  • 19,820
  • 10
  • 87
  • 120
  • I'm being picky/curious here, but why are you setting `style: .Default` for the `Cancel` button and `style: .Cancel` for the *other* button? Is it just a mistake or do you do it with a purpose? – SaltyNuts Jul 17 '15 at 17:52
  • Think I just wanted one to stand out more than the other, thats all. Like bold vs normal font. – DogCoffee Jul 17 '15 at 20:43
  • @DogCoffee I'm a bit confused about the permission prompt for accessing the camera initially. Is that not something in built with iOS that the developers cannot mimic? We can only check if it has been denied before and then prompt to update in settings?? – user2363025 Oct 27 '15 at 10:56
  • I made this a while back, the app needed the camera for my QR reader. iOS does ask if the camera can be used. I just wanted the user to know why. Most the the time when things pop up asking for this or that I know myself i usually deny first. This was just my way to say - pretty please accept. – DogCoffee Oct 27 '15 at 11:26
  • @DogCoffee So I don't need to actually make a function for first time access permission prompt, i can let iOS do that in the background and I just need to cater for if they have denied permissions in the past? – user2363025 Oct 27 '15 at 11:43
  • @user2363025 totally up to you. Try you way and if the workflow makes sense then thats fine. But if you need the camera and the user has selected don't allow camera, then you have to account for that otherwise your app will crash – DogCoffee Oct 28 '15 at 00:11
16

None of the answers seem to check for both microphone and camera permissions. Our code checks against the scenario where camera permissions are granted but microphone access is denied.

Since we're new to Swift, it's unlikely the gnarly nested closures and if statements are optimal. Please share suggestions for improving the code! But at least it works so far in testing.

    AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (videoGranted: Bool) -> Void in
        if (videoGranted) {
            AVCaptureDevice.requestAccessForMediaType(AVMediaTypeAudio, completionHandler: { (audioGranted: Bool) -> Void in
                if (audioGranted) {
                    dispatch_async(dispatch_get_main_queue()) {
                        // Both video & audio granted
                    }
                } else {
                    // Rejected audio
                }
            })
        } else {
            // Rejected video
        }
    })
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • 1
    Only answer that addresses both video and audio permissions. Side note, its crazy to me that you cannot ask for both of these as a combination, thanks apple. – lostintranslation May 27 '16 at 02:09
  • This is the correct answer. For video recording, this is critical, otherwise the system will launch audio permissions on its own when you initialize the session. Then your app isn't going to know about it, and it essentially "bricks" the experience for the user. I've noticed a lot of projects just ignore this and boy oh boy does it generate a large number of customer support tickets :) – Ryan Romanchuk Jul 06 '18 at 16:41
8
  • Swift 3.0 Solution

    import AVFoundation

Note: add Privacy - Camera Usage Description key on your Info.plist

//MARK: Camera Handling

        func callCamera(){
            let myPickerController = UIImagePickerController()
            myPickerController.delegate = self;
            myPickerController.sourceType = UIImagePickerControllerSourceType.camera

            self.present(myPickerController, animated: true, completion: nil)
            NSLog("Camera");
        }
        func checkCamera() {
            let authStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
            switch authStatus {
            case .authorized: callCamera() // Do your stuff here i.e. callCameraMethod()
            case .denied: alertToEncourageCameraAccessInitially()
            case .notDetermined: alertPromptToAllowCameraAccessViaSetting()
            default: alertToEncourageCameraAccessInitially()
            }
        }

        func alertToEncourageCameraAccessInitially() {
            let alert = UIAlertController(
                title: "IMPORTANT",
                message: "Camera access required for capturing photos!",
                preferredStyle: UIAlertControllerStyle.alert
            )
            alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
            alert.addAction(UIAlertAction(title: "Allow Camera", style: .cancel, handler: { (alert) -> Void in
                UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
            }))
            present(alert, animated: true, completion: nil)
        }

        func alertPromptToAllowCameraAccessViaSetting() {

            let alert = UIAlertController(
                title: "IMPORTANT",
                message: "Camera access required for capturing photos!",
                preferredStyle: UIAlertControllerStyle.alert
            )
            alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel) { alert in
                if AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo).count > 0 {
                    AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { granted in
                        DispatchQueue.main.async() {
                            self.checkCamera() } }
                }
                }
            )
            present(alert, animated: true, completion: nil)
        }
Sourabh Sharma
  • 8,222
  • 5
  • 68
  • 78
  • You have switched the methods ;-) The 'via setting' launches perm dialog, and the second one launches settings :-) – matrejek Oct 06 '17 at 07:43
5

For Swift 3, you can add this on your viewWillAppear method of your first view controller:

First import the AVFoundation framework

import AVFoundation

Then:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    let authorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)

    switch authorizationStatus {
    case .notDetermined:
        AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { granted in
            if granted {
                print("access granted")
            }
            else {
                print("access denied")
            }
        }
    case .authorized:
        print("Access authorized")
    case .denied, .restricted:
        print("restricted")

    }
}

Don't forget to add Privacy - Camera Usage Description key on your Info.plist

pableiros
  • 14,932
  • 12
  • 99
  • 105
4

For me this work on iOS7 and iOS8:

    ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];

    switch (status) {
        case ALAuthorizationStatusAuthorized:
            break;

        case ALAuthorizationStatusRestricted:
        case ALAuthorizationStatusDenied:
            break;

        case ALAuthorizationStatusNotDetermined:
            break;
    }
Michael
  • 32,527
  • 49
  • 210
  • 370
3

I make an access check on the app delegate.

import UIKit
import AVFoundation
import Photos

        func applicationDidBecomeActive(application: UIApplication) {
            cameraAllowsAccessToApplicationCheck()
            internetAvailabilityOnApplicationCheck()
            photoLibraryAvailabilityCheck()
        }

    //MARK:- CAMERA ACCESS CHECK
        func cameraAllowsAccessToApplicationCheck()
        {
            let authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
            switch authorizationStatus {
            case .NotDetermined:
                // permission dialog not yet presented, request authorization
                AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo,
                    completionHandler: { (granted:Bool) -> Void in
                        if granted {
                            print("access granted")
                        }
                        else {
                            print("access denied")
                        }
                })
            case .Authorized:
                print("Access authorized")
            case .Denied, .Restricted:
            alertToEncourageCameraAccessWhenApplicationStarts()
            default:
                print("DO NOTHING")
            }
        }
        //MARK:- PHOTO LIBRARY ACCESS CHECK
        func photoLibraryAvailabilityCheck()
        {
            if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized
            {

            }
            else
            {
                var cameraUnavailableAlertController = UIAlertController (title: "Photo Library Unavailable", message: "Please check to see if device settings doesn't allow photo library access", preferredStyle: .Alert)

                var settingsAction = UIAlertAction(title: "Settings", style: .Destructive) { (_) -> Void in
                    let settingsUrl = NSURL(string:UIApplicationOpenSettingsURLString)
                    if let url = settingsUrl {
                        UIApplication.sharedApplication().openURL(url)
                    }
                }
                var cancelAction = UIAlertAction(title: "Okay", style: .Default, handler: nil)
                cameraUnavailableAlertController .addAction(settingsAction)
                cameraUnavailableAlertController .addAction(cancelAction)
                self.window?.rootViewController!.presentViewController(cameraUnavailableAlertController , animated: true, completion: nil)
            }
        }
        func internetAvailabilityOnApplicationCheck()
        {
            //MARK:- INTERNET AVAILABLITY
            if InternetReachability.isConnectedToNetwork() {

            }
            else
            {
                dispatch_async(dispatch_get_main_queue(), {

                    //INTERNET NOT AVAILABLE ALERT
                    var internetUnavailableAlertController = UIAlertController (title: "Network Unavailable", message: "Please check your internet connection settings and turn on Network Connection", preferredStyle: .Alert)

                    var settingsAction = UIAlertAction(title: "Settings", style: .Destructive) { (_) -> Void in
                        let settingsUrl = NSURL(string:UIApplicationOpenSettingsURLString)
                        if let url = settingsUrl {
                            UIApplication.sharedApplication().openURL(url)
                        }
                    }
                    var cancelAction = UIAlertAction(title: "Okay", style: .Default, handler: nil)
                    internetUnavailableAlertController .addAction(settingsAction)
                    internetUnavailableAlertController .addAction(cancelAction)
                    self.window?.rootViewController!.presentViewController(internetUnavailableAlertController , animated: true, completion: nil)
                })
            }
        }

*

CenterOrbit
  • 6,446
  • 1
  • 28
  • 34
Alvin George
  • 14,148
  • 92
  • 64
2

The issue for me was that Bundle name and Bundle Display Name were not getting set in my Info.plist due to some recent build configuration changes. Kind of an unlikely case... But it took me a few hours to nail this down. Hopefully it helps for someone else.

Chris Wagner
  • 20,773
  • 8
  • 74
  • 95