Introduction
To check if a user has granted camera permission, use platform-specific APIs: AVCaptureDevice.authorizationStatus(for: .video) on iOS (Swift), ContextCompat.checkSelfPermission() on Android (Kotlin), and the navigator.permissions.query() or navigator.mediaDevices.getUserMedia() APIs in web browsers (JavaScript). Each platform has its own permission model — iOS and Android require declaring permissions in manifest/plist files, while browsers prompt users automatically on first access.
iOS (Swift)
Check Current Permission Status
1import AVFoundation
2
3func checkCameraPermission() {
4 let status = AVCaptureDevice.authorizationStatus(for: .video)
5
6 switch status {
7 case .authorized:
8 print("Camera access granted")
9 openCamera()
10
11 case .notDetermined:
12 // User hasn't been asked yet — request permission
13 AVCaptureDevice.requestAccess(for: .video) { granted in
14 DispatchQueue.main.async {
15 if granted {
16 self.openCamera()
17 } else {
18 self.showPermissionDeniedAlert()
19 }
20 }
21 }
22
23 case .denied:
24 // User previously denied — direct to Settings
25 showPermissionDeniedAlert()
26
27 case .restricted:
28 // Parental controls or device policy prevents access
29 print("Camera access restricted")
30
31 @unknown default:
32 break
33 }
34}
Request Permission and Handle Response
1func requestCameraAccess(completion: @escaping (Bool) -> Void) {
2 AVCaptureDevice.requestAccess(for: .video) { granted in
3 DispatchQueue.main.async {
4 completion(granted)
5 }
6 }
7}
8
9// Direct user to Settings if denied
10func showPermissionDeniedAlert() {
11 let alert = UIAlertController(
12 title: "Camera Access Required",
13 message: "Please enable camera access in Settings to use this feature.",
14 preferredStyle: .alert
15 )
16
17 alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
18 if let url = URL(string: UIApplication.openSettingsURLString) {
19 UIApplication.shared.open(url)
20 }
21 })
22
23 alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
24
25 present(alert, animated: true)
26}
Add to Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need camera access to take photos for your profile.</string>
Android (Kotlin)
Check and Request Permission
1import android.Manifest
2import android.content.pm.PackageManager
3import androidx.activity.result.contract.ActivityResultContracts
4import androidx.core.content.ContextCompat
5
6class CameraActivity : AppCompatActivity() {
7
8 private val cameraPermissionLauncher = registerForActivityResult(
9 ActivityResultContracts.RequestPermission()
10 ) { isGranted ->
11 if (isGranted) {
12 openCamera()
13 } else {
14 showPermissionDeniedMessage()
15 }
16 }
17
18 fun checkCameraPermission() {
19 when {
20 ContextCompat.checkSelfPermission(
21 this, Manifest.permission.CAMERA
22 ) == PackageManager.PERMISSION_GRANTED -> {
23 openCamera()
24 }
25
26 shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
27 // User previously denied — show explanation before asking again
28 showRationale()
29 }
30
31 else -> {
32 // First time or "Don't ask again" not checked
33 cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
34 }
35 }
36 }
37}
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
Handling "Don't Ask Again"
1fun showPermissionDeniedMessage() {
2 if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
3 // User checked "Don't ask again" — direct to app settings
4 val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
5 data = Uri.fromParts("package", packageName, null)
6 }
7 startActivity(intent)
8 } else {
9 Toast.makeText(this, "Camera permission is required", Toast.LENGTH_SHORT).show()
10 }
11}
Web Browser (JavaScript)
Using Permissions API
1async function checkCameraPermission() {
2 try {
3 const result = await navigator.permissions.query({ name: 'camera' });
4
5 switch (result.state) {
6 case 'granted':
7 console.log('Camera permission granted');
8 startCamera();
9 break;
10
11 case 'prompt':
12 console.log('Permission not yet requested');
13 requestCameraAccess();
14 break;
15
16 case 'denied':
17 console.log('Camera permission denied');
18 showPermissionInstructions();
19 break;
20 }
21
22 // Listen for permission changes
23 result.addEventListener('change', () => {
24 console.log(`Permission changed to: ${result.state}`);
25 });
26 } catch (error) {
27 // Permissions API not supported — fall back to getUserMedia
28 requestCameraAccess();
29 }
30}
1async function requestCameraAccess() {
2 try {
3 const stream = await navigator.mediaDevices.getUserMedia({ video: true });
4
5 // Permission granted — use the stream
6 const videoElement = document.getElementById('camera-preview');
7 videoElement.srcObject = stream;
8 videoElement.play();
9 } catch (error) {
10 if (error.name === 'NotAllowedError') {
11 console.log('User denied camera access');
12 } else if (error.name === 'NotFoundError') {
13 console.log('No camera device found');
14 } else if (error.name === 'NotReadableError') {
15 console.log('Camera is in use by another application');
16 } else {
17 console.error('Camera error:', error);
18 }
19 }
20}
21
22// Stop the camera when done
23function stopCamera(stream) {
24 stream.getTracks().forEach(track => track.stop());
25}
Permission Status Summary
| Platform | Check API | Request API | Manifest Entry |
| iOS | AVCaptureDevice.authorizationStatus | AVCaptureDevice.requestAccess | NSCameraUsageDescription in Info.plist |
| Android | ContextCompat.checkSelfPermission | ActivityResultContracts.RequestPermission | CAMERA in AndroidManifest.xml |
| Web | navigator.permissions.query | navigator.mediaDevices.getUserMedia | None |
Common Pitfalls
Not adding the usage description string on iOS: Without NSCameraUsageDescription in Info.plist, the app crashes immediately when requesting camera access. Apple requires a human-readable reason for every permission request.
Using deprecated onRequestPermissionsResult on Android: The callback-based requestPermissions() approach is deprecated. Use registerForActivityResult(ActivityResultContracts.RequestPermission()) instead, which is lifecycle-aware and cleaner.
Assuming getUserMedia rejection always means "denied": In web browsers, getUserMedia can fail for multiple reasons — device not found (NotFoundError), camera in use (NotReadableError), or insecure context (SecurityError). Always check error.name to handle each case differently.
Not handling the "Don't ask again" state on Android: After the user selects "Don't ask again", requestPermission silently returns denied without showing a dialog. Check shouldShowRequestPermissionRationale() and direct users to app settings when it returns false after a denial.
Requesting camera permission before explaining why: Users are more likely to grant permission when they understand the reason. Show an explanation screen or rationale dialog before calling the system permission prompt, especially on the first request.
Summary
On iOS, use AVCaptureDevice.authorizationStatus(for: .video) and handle all four states (authorized, notDetermined, denied, restricted)
On Android, use ContextCompat.checkSelfPermission with ActivityResultContracts.RequestPermission
On the web, use navigator.permissions.query({name: 'camera'}) or try getUserMedia directly
Always declare permissions in the platform's manifest file (Info.plist, AndroidManifest.xml)
Handle the "denied" state by directing users to system settings to re-enable the permission