Circadify

Methods

Reference for all Circadify iOS SDK methods.

Reference for the public API of the Circadify iOS SDK. The camera is owned by a CircadifyCamera instance that is shared between the on-screen preview (CircadifyCameraPreview) and measurement (CircadifySDK.measureVitals(camera:)), so the scan records the exact session the user is looking at.

CircadifyCamera

@MainActor @Observable final class — owns the single AVCaptureSession shared by the preview and the measurement. Create one, bind it to a preview, start it to go live, and hand it to measureVitals(camera:).

@State private var camera = CircadifyCamera()              // front camera by default
// or
@State private var camera = CircadifyCamera(position: .back)
swift

init(position:)

public init(position: Position = .front)
 
public enum Position: Sendable { case front, back }
swift

Defaults to .front (the selfie camera), which is what almost every face-scan flow wants — the user frames their own face in the live preview. Pass .back to capture from the rear camera, which suits measuring another person or a kiosk / tripod setup. The back-camera preview is not mirrored, and the phone's screen no longer acts as a fill light for the subject (it faces away), so ensure good ambient lighting. Both positions feed the identical on-device pipeline and produce the same measurements.

authorizationStatus

public private(set) var authorizationStatus: AVAuthorizationStatus
swift

Read-only and observable. SwiftUI views that read this property re-render automatically when permission changes. Prefer this over the SDK's checkCameraPermission() convenience.

isRunning

public private(set) var isRunning: Bool
swift

Read-only and observable. true once start() has brought the session live, false after stop(). Drive UI (preview overlays, scan buttons) off this property.

onQualityWarning

public var onQualityWarning: ((QualityWarning) -> Void)?
swift

Quality feedback lives on the camera, not the SDK. The closure is delivered on the main thread and fires both while the user is framing themselves and during an active measurement, so you can surface guidance ("Move into better lighting", "Hold still") the whole time the camera is live.

camera.onQualityWarning = { warning in
    guidance = warning.message   // lighting / motion / framing / occlusion tip
}
swift

See QualityWarning below for the payload.

requestAccess()

@discardableResult public func requestAccess() async -> Bool
swift

Requests camera permission, showing the system dialog when the status is .notDetermined. Returns true if access is granted, and refreshes the camera's observable authorizationStatus. This is the preferred way to request access. (The SDK's requestCameraPermission() is a separate convenience that calls AVCaptureDevice directly and does not update a camera's observable state.)

let granted = await camera.requestAccess()
swift
Caution

iOS only shows the permission dialog once. If the user denies it, subsequent calls return false without showing a dialog. Guide the user to Settings > Privacy > Camera to re-enable access.

start()

public func start() async throws
swift

Brings the shared session live: it requests permission if needed, configures the capture device, and starts streaming frames to the preview. On success, isRunning becomes true.

Throws:

  • CircadifyError.cameraPermissionDenied — the user has denied camera access.
  • CircadifyError.cameraNotAvailable — no camera for the requested position (e.g. running on the Simulator, which has no camera).
  • CircadifyError.cameraInUse — the camera is already in use by another session.
do {
    try await camera.start()
} catch {
    print("Could not start camera: \(error)")
}
swift

stop()

public func stop()
swift

Stops the session and releases the camera. Call this in onDisappear so you don't hold the camera when the view leaves the screen. Sets isRunning to false.

CircadifyCameraPreview

A SwiftUI UIViewRepresentable that renders the camera's shared session — kept upright via the iOS 17 RotationCoordinator, and mirrored for the front camera (the rear camera renders unmirrored). It binds to the same CircadifyCamera you measure with, so what the user sees is exactly what gets recorded.

public init(
    camera: CircadifyCamera,
    showGuide: Bool = true,
    isScanning: Bool = false,
    style: CircadifyScanStyle = CircadifyScanStyle()
)
swift
  • camera — the CircadifyCamera whose session to display. Required.
  • showGuide — draws the branded scan overlay: a thermal heat-glow that tracks the user's face plus a thin guide oval (not just a static outline). Defaults to true, and turns on the camera's face tracking automatically while the preview is on screen.
  • isScanning — switches the glow into its hotter, faster measuring state. Defaults to false; bind it to your "measurement in progress" flag.
  • style — overrides the overlay's colors and timings (CircadifyScanStyle). Defaults to the Circadify thermal palette.
CircadifyCameraPreview(camera: camera)
    .ignoresSafeArea()
 
// heat the glow up while a measurement runs
CircadifyCameraPreview(camera: camera, isScanning: isScanning)
 
// render a clean preview with no overlay
CircadifyCameraPreview(camera: camera, showGuide: false)
swift

See Scan Overlay for theming the glow with CircadifyScanStyle and drawing your own overlay from camera.scanFace.

measureVitals(camera:options:)

public func measureVitals(
    camera: CircadifyCamera,
    options: MeasurementOptions = MeasurementOptions()
) async throws -> VitalSignsResult
swift

Records the live session owned by camera and runs the full measurement flow — capture, upload, and result retrieval — returning vital signs when complete. The camera argument is required: pass the same instance that backs your CircadifyCameraPreview so the scan captures the session the user is looking at.

With demographics for improved accuracy:

let result = try await sdk.measureVitals(
    camera: camera,
    options: MeasurementOptions(
        demographics: Demographics(age: 35, sex: .male, fitzpatrick: 3)
    )
)
swift

See Configuration → Measurement Options for the full Demographics type definition.

Caution

The SDK does not fabricate or estimate fallback vitals when a measurement fails — it throws the real CircadifyError. Always handle errors and prompt the user to retry under better conditions.

SwiftUI Usage

This is the canonical end-to-end flow. A single CircadifyCamera backs the preview, the quality-warning guidance, and the measurement.

import SwiftUI
import CircadifySDK
 
struct ScanView: View {
    @State private var camera = CircadifyCamera()
    @State private var result: VitalSignsResult?
    @State private var isScanning = false
    @State private var guidance: String?
 
    private let sdk = try! CircadifySDK(apiKey: "ck_live_your_key_here")
 
    var body: some View {
        ZStack {
            CircadifyCameraPreview(camera: camera)
                .ignoresSafeArea()
 
            VStack {
                Spacer()
                if let guidance { Text(guidance).foregroundStyle(.white) }
                Button(isScanning ? "Scanning…" : "Start scan") {
                    Task { await scan() }
                }
                .disabled(isScanning)
            }
        }
        .task {
            camera.onQualityWarning = { guidance = $0.message }   // lighting / motion / framing tips
            try? await camera.start()                              // prompts for permission, goes live
        }
        .onDisappear { camera.stop() }
    }
 
    func scan() async {
        isScanning = true
        defer { isScanning = false }
        do {
            result = try await sdk.measureVitals(camera: camera)   // records the SAME live session
        } catch let error as CircadifyError {
            print("Scan failed: \(error.errorDescription ?? "")")
        } catch {
            print("Scan failed: \(error)")
        }
    }
}
swift

Returns: VitalSignsResult

public struct VitalSignsResult: Codable, Equatable, Sendable {
    public let heartRate: Int          // BPM
    public let hrv: Double?            // Heart rate variability (ms)
    public let respiratoryRate: Int?   // Breaths per minute
    public let spo2: Double?           // Blood oxygen saturation (%)
    public let systolicBp: Int?        // Systolic blood pressure (mmHg)
    public let diastolicBp: Int?       // Diastolic blood pressure (mmHg)
    public let confidence: Double      // 0-1 reliability score
    public let sessionId: String
    public let timestamp: Date
}
swift
Tip

A confidence above 0.7 indicates a reliable measurement. Below 0.4 suggests quality issues; prompt the user to retry under better conditions.

UIKit Usage

There is no UIKit-specific preview or measurement API. From UIKit, host the SwiftUI CircadifyCameraPreview inside a UIHostingController (or build your own preview layer from the camera's session), and call the same measureVitals(camera:) with the shared CircadifyCamera.

let camera = CircadifyCamera()
let preview = UIHostingController(rootView: CircadifyCameraPreview(camera: camera))
addChild(preview)
view.addSubview(preview.view)
// pin preview.view, then preview.didMove(toParent: self)
 
Task {
    try await camera.start()
    let result = try await sdk.measureVitals(camera: camera)
    // update UI on the main actor
}
swift

checkCameraPermission()

A convenience wrapper on the SDK that returns the current camera authorization status without prompting. Prefer the camera's observable authorizationStatus when you have a CircadifyCamera.

let status = sdk.checkCameraPermission()
 
switch status {
case .authorized:
    // Ready to scan
case .notDetermined:
    // Need to request permission
case .denied, .restricted:
    // Cannot scan — guide user to Settings
@unknown default:
    break
}
swift

Returns: AVAuthorizationStatus

requestCameraPermission()

A convenience wrapper on the SDK that requests camera permission, showing the system dialog if status is .notDetermined. Prefer the camera's requestAccess() when you have a CircadifyCamera.

let granted = await sdk.requestCameraPermission()
 
if granted {
    try await camera.start()
} else {
    // Handle denial
}
swift

Returns: Booltrue if permission was granted.

Caution

iOS only shows the permission dialog once. If the user denies it, subsequent calls return false without showing a dialog. Guide the user to Settings > Privacy > Camera to re-enable access.

cancel()

Cancels a measurement in progress. The in-flight measureVitals(camera:) call throws CircadifyError.cancelled.

// Start measurement in a task
let task = Task {
    try await sdk.measureVitals(camera: camera)
}
 
// Cancel from elsewhere (e.g., a button tap)
sdk.cancel()
 
// The task will throw CircadifyError.cancelled
do {
    let result = try await task.value
} catch let error as CircadifyError where error == .cancelled {
    print("Scan cancelled by user")
}
swift

QualityWarning type

Delivered to camera.onQualityWarning on the main thread.

public struct QualityWarning: Sendable {
    public let type: WarningType     // .lighting / .motion / .facePosition / .occlusion
    public let message: String       // human-readable guidance
    public let severity: Severity    // .low / .medium / .high
}
swift

Next Steps