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)swiftinit(position:)
public init(position: Position = .front)
public enum Position: Sendable { case front, back }swiftDefaults 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: AVAuthorizationStatusswiftRead-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: BoolswiftRead-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)?swiftQuality 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
}swiftSee QualityWarning below for the payload.
requestAccess()
@discardableResult public func requestAccess() async -> BoolswiftRequests 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()swiftiOS 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 throwsswiftBrings 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)")
}swiftstop()
public func stop()swiftStops 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()
)swiftcamera— theCircadifyCamerawhose 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 totrue, 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 tofalse; 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)swiftSee 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 -> VitalSignsResultswiftRecords 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)
)
)swiftSee Configuration → Measurement Options for the full Demographics type definition.
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)")
}
}
}swiftReturns: 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
}swiftA 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
}swiftcheckCameraPermission()
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
}swiftReturns: 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
}swiftReturns: Bool — true if permission was granted.
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")
}swiftQualityWarning 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
}swiftNext Steps
- Configuration — Callbacks and options
- Camera & Permissions — Permission handling in depth
- Error Handling — Handle all failure cases