Request Camera Permission Dialog Priming (Prime Permissions) in iOS
Asked Answered
S

2

15

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

Stanchion answered 23/12, 2016 at 15:6 Comment(0)
S
30

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)
}
Stanchion answered 23/12, 2016 at 15:6 Comment(0)
S
0

Suppose we have two buttons (one for picking picture from library another from camera) with tags 1,2 that are linked to action:

import UIKit
import AVFoundation

    @IBAction func changeImage(sender: UIButton) {
        let picker = UIImagePickerController()
        if sender.tag == 2 { // tag = 2 for camera button. tag = 1 for image picker
            guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return }
            let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
            
            switch cameraAuthorizationStatus {
            case .notDetermined:
                requestCameraPermission()
                return
            case .authorized:
                break
            case .restricted, .denied:
                alertCameraAccessNeeded()
                return
            @unknown default:
                return
            }
            picker.sourceType = .camera
        }
        
        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
    }

    private func requestCameraPermission() {
        AVCaptureDevice.requestAccess(for: .video) { [weak self] accessGranted in
            if !accessGranted {
                DispatchQueue.main.async {
                    self?.alertCameraAccessNeeded()
                }
            }
        }
    }
    
    private  func alertCameraAccessNeeded() {
        guard let settingsAppURL = URL(string: UIApplication.openSettingsURLString),
              UIApplication.shared.canOpenURL(settingsAppURL) else { return } // This should never happen
        let alert = UIAlertController(
            title: "Need Camera Access",
            message: "Camera access is required to take pictures of item.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Cancel", style: .default))
        alert.addAction(UIAlertAction(title: "Allow Camera", style: .cancel) { _ in
            UIApplication.shared.open(settingsAppURL, options: [:])
        })
        present(alert, animated: true)
    }
Sharpset answered 7/1, 2022 at 11:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.