Request Camera Permission Dialog Priming (Prime Permissions) in iOS

cleverbit picture cleverbit · Dec 23, 2016 · Viewed 12.4k times · Source

What is the most effective way to prompt a User to provide access to the Camera (or other feature), while ensuring the best experience?

When accessing the Camera, iOS must ask the Customer permission to allow access. As we all know, if the Customer says "No" but then changes their mind, there is no way to reverse this decision from within your App. They must go to Settings and follow a number of steps to re-enable access, namely:

Settings -> Privacy -> Camera -> [Your App] -> turn switch on

Answer

cleverbit picture cleverbit · Dec 23, 2016

Permission Priming is an effective way to avoid a situation where your Customer might deny access to a key feature of your app.

On iOS an App is only allowed to trigger the default system permission once per feature. Permission priming is when an app "primes" the Customer with an alert that mimics a system permission.

The benefit to doing this is so that if the Customer opts-out (selects Cancel), the App is still able to ask again in future, until they say yes — at which time the actual system permission is displayed and the Customer is statistically less likely to then change their mind and enter into the negative work flow.

Furthermore, since cameraSelected() performs this workflow, if the user declines, but then at some future point does change their settings, the App will immediately reflect the new permissions without further input (ie. the User could switch to Settings, change permissions, and then switch back to the App).

Here is some Swift 3 code to implement this feature:

[UPDATE: Included is a solution to open a deep-link to Settings where the User can enable camera access, if they have previously denied it.]

[UPDATE 2: Added sample lines for Analytics implementation.]

func cameraSelected() {
    // First we check if the device has a camera (otherwise will crash in Simulator - also, some iPod touch models do not have a camera).
    if let deviceHasCamera = UIImagePickerController.isSourceTypeAvailable(.camera) {
        let authStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
        switch authStatus {
            case .authorized:
                showCameraPicker()
            case .denied:
                alertPromptToAllowCameraAccessViaSettings()
            case .notDetermined:
                permissionPrimeCameraAccess()
            default:
                permissionPrimeCameraAccess()
        }
    } else {
        let alertController = UIAlertController(title: "Error", message: "Device has no camera", preferredStyle: .alert)
        let defaultAction = UIAlertAction(title: "OK", style: .default, handler: { (alert) in
            Analytics.track(event: .permissionsPrimeCameraNoCamera)
        })
        alertController.addAction(defaultAction)
        present(alertController, animated: true, completion: nil)
    }
}


func alertPromptToAllowCameraAccessViaSettings() {
    let alert = UIAlertController(title: "\"<Your App>\" Would Like To Access the Camera", message: "Please grant permission to use the Camera so that you can  <customer benefit>.", preferredStyle: .alert )
    alert.addAction(UIAlertAction(title: "Open Settings", style: .cancel) { alert in
        Analytics.track(event: .permissionsPrimeCameraOpenSettings)
        if let appSettingsURL = NSURL(string: UIApplicationOpenSettingsURLString) {
          UIApplication.shared.openURL(appSettingsURL)
        }
    })
    present(alert, animated: true, completion: nil)
}


func permissionPrimeCameraAccess() {
    let alert = UIAlertController( title: "\"<Your App>\" Would Like To Access the Camera", message: "<Your App> would like to access your Camera so that you can <customer benefit>.", preferredStyle: .alert )
    let allowAction = UIAlertAction(title: "Allow", style: .default, handler: { (alert) -> Void in
        Analytics.track(event: .permissionsPrimeCameraAccepted)
        if AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo).count > 0 {
            AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { [weak self] granted in
                DispatchQueue.main.async {
                    self?.cameraSelected() // try again
                }
            })
        }
    })
    alert.addAction(allowAction)
    let declineAction = UIAlertAction(title: "Not Now", style: .cancel) { (alert) in
        Analytics.track(event: .permissionsPrimeCameraCancelled)
    }
    alert.addAction(declineAction)
    present(alert, animated: true, completion: nil)
}


func showCameraPicker() {
    let picker = UIImagePickerController()
    picker.delegate = self
    picker.modalPresentationStyle = UIModalPresentationStyle.currentContext
    picker.allowsEditing = false
    picker.sourceType = UIImagePickerControllerSourceType.camera
    present(picker, animated: true, completion: nil)
}