Circadify

Camera & Permissions

Handle camera permissions correctly in your iOS app when using the Circadify SDK.

The Circadify SDK requires camera access — the front (selfie) camera by default, or the rear camera when you create it with CircadifyCamera(position: .back). The permission flow is identical either way. iOS enforces strict permission controls — your app must handle the permission flow before starting a measurement.

Info.plist

Add the camera usage description to your Info.plist. Without this, iOS will terminate your app on camera access.

<key>NSCameraUsageDescription</key>
<string>This app uses the camera to measure your vital signs contactlessly.</string>
xml

Write a clear, specific description. Vague descriptions like "Camera access needed" may cause App Store review rejection.

Permission Flow

Permissions are handled through CircadifyCamera — the object that owns the shared capture session for both the live preview and the measurement.

  1. Check the current status with camera.authorizationStatus (an observable AVAuthorizationStatus).

  2. Request access with await camera.requestAccess(). This shows the system dialog when the status is .notDetermined and returns whether access was granted.

  3. Start the camera with try await camera.start(). If the status is .notDetermined, start() requests access automatically — you don't have to call requestAccess() first. If access is denied, start() throws CircadifyError.cameraPermissionDenied.

  4. Handle denial — catch .cameraPermissionDenied and guide the user to Settings. iOS won't show the dialog again.

In the simplest case you don't manage permissions yourself at all — just call camera.start() in a .task and let it prompt:

import SwiftUI
import CircadifySDK
 
struct ScanView: View {
    @State private var camera = CircadifyCamera()
 
    var body: some View {
        CircadifyCameraPreview(camera: camera)
            .ignoresSafeArea()
            .task {
                try? await camera.start()   // requests permission if needed, then goes live
            }
            .onDisappear { camera.stop() }
    }
}
swift

When you want to react to denial — for example to deep-link the user into Settings — observe camera.authorizationStatus and surface an alert.

Drive the camera from a SwiftUI view. The preview renders the shared session, .task starts the camera (prompting for permission), and a denied status triggers a Settings deep-link alert. Returning from Settings re-checks via scenePhase.

import SwiftUI
import CircadifySDK
 
struct ScanView: View {
    @State private var camera = CircadifyCamera()
    @State private var showSettingsAlert = false
    @Environment(\.scenePhase) private var scenePhase
 
    var body: some View {
        ZStack {
            CircadifyCameraPreview(camera: camera)
                .ignoresSafeArea()
 
            if !camera.isRunning {
                Text("Camera access is required to scan.")
                    .foregroundStyle(.white)
            }
        }
        .alert("Camera Access Required", isPresented: $showSettingsAlert) {
            Button("Open Settings") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            }
            Button("Cancel", role: .cancel) { }
        } message: {
            Text("Go to Settings > Privacy > Camera and enable access for this app.")
        }
        .task { await start() }
        .onChange(of: scenePhase) { _, phase in
            // Returning from Settings — retry if the user just enabled access.
            if phase == .active, !camera.isRunning {
                Task { await start() }
            }
        }
        .onDisappear { camera.stop() }
    }
 
    func start() async {
        do {
            try await camera.start()   // prompts if .notDetermined, then goes live
        } catch CircadifyError.cameraPermissionDenied {
            showSettingsAlert = true
        } catch {
            print("Camera failed to start: \(error)")
        }
    }
}
swift
Note

CircadifySDK also exposes sdk.checkCameraPermission() and await sdk.requestCameraPermission() as standalone convenience methods that query/request AVCaptureDevice directly. Unlike camera.requestAccess(), they do not refresh a CircadifyCamera's observable authorizationStatus — so prefer camera.authorizationStatus and camera.requestAccess() when you have a CircadifyCamera on hand.

Best Practices

  • Let start() do the work. camera.start() requests permission automatically when the status is .notDetermined, so you rarely need to call requestAccess() yourself. Catch CircadifyError.cameraPermissionDenied to handle the denied case.
  • Request early. Trigger the prompt during onboarding or as soon as the scan screen appears, not at the last moment before measuring.
  • Handle denial gracefully. Provide a clear path to Settings with a direct deep link (UIApplication.openSettingsURLString).
  • Re-check on foreground. After the user returns from Settings, re-check on scenePhase == .active (SwiftUI) or didBecomeActiveNotification (UIKit) and retry camera.start().
  • Stop when off-screen. Call camera.stop() in onDisappear / viewDidDisappear to release the capture session.
Note

iOS only shows the system permission dialog once per app install. If the user dismisses or denies it, the only way to re-enable camera access is through Settings.

Next Steps