Skip to main content
This page takes you from an empty project to a completed reading (heart rate, HRV (RMSSD), breathing rate and related measures) on iOS 15 or later. The SDK is a Swift module named NeurofitVitals. All public types are Sendable, and events are delivered on the main thread.
1

Add the package

Binary license. Your delivery contains NeurofitVitals.xcframework (and a zip with its SwiftPM checksum). Either drag it into your app target (Frameworks, Libraries and Embedded Content, set to Embed and Sign), or reference it from a local Swift package with a binaryTarget:
Package.swift
Source license. Add the SDK folder as a local package dependency and link the NeurofitVitals product. The package depends on the PpgCore engine package that ships next to it (ppgcore/ports/swift), and the sources use access-level imports (internal import PpgCore), so building them needs Xcode 16 or newer.
Package.swift
The package manifest uses Swift tools 5.9, platforms: [.iOS(.v15)] and Swift 5 language mode; the product is a dynamic library. There are no dependencies beyond Apple frameworks.
2

Declare camera usage

Add NSCameraUsageDescription to your Info.plist. iOS shows this text in the permission prompt.
Info.plist
The SDK samples the accelerometer through Core Motion to help estimate breathing rate. That does not show a permission prompt; adding NSMotionUsageDescription is still recommended, and name the accelerometer in your consent copy. See Privacy and compliance.
Info.plist
3

Activate the SDK

Call activate(licenseKey:) once, early in the app’s life, before you create a session. Verification is offline and fast; call it synchronously at launch. The call throws when the key is malformed, signed by another key, issued for a different bundle identifier, or does not cover this SDK build’s date.
With the binary xcframework the key must list your bundle identifier, including any development identifier you run under; only a Debug build of the SDK sources relaxes that check to a warning. See License keys for the key format, updatesUntil and the debug behaviour.
4

Check device support and camera access

start() also prompts when access is not yet determined, so the explicit request is for your own flow (a rationale screen before the system prompt).
5

Create a session and listen for events

A VitalsSession owns the camera, the torch and one reading at a time. Consume events as an AsyncStream, or set a delegate if you prefer callbacks. Both deliver on the main thread.
start() turns on the camera and torch and moves the session to positioning. It throws .alreadyRunning if called while running, .notActivated if activation was lost, .cameraDenied, .cameraRestricted or .cameraUnavailable for camera problems, .unsupportedDevice(reason:) when the device cannot sustain a reading, and .internalError on a session that was stopped or has finished. A failed start() leaves the session idle, so you can call it again after fixing the cause. VitalsSession.init itself throws .notActivated, .unsupportedDevice(reason:) or .internalError("Invalid configuration: ...").
6

Show the camera preview (optional)

The session exposes an AVCaptureVideoPreviewLayer you can place in your own view. Many apps show a small red circle of the fingertip so the user sees their pulse in the glow.
7

Use the result

VitalsResult is Codable, so you can store or upload it as JSON. Field meanings, units and how to treat the withheld tier are on the Metrics page.

Guidance copy

Guidance is an enum; the SDK does not ship strings. Map each case to your own localized copy. The NEUROFIT app’s copy is listed on the Reading lifecycle page as a starting point.

Lifecycle notes

  • Call stop() when your reading screen disappears. It is idempotent, turns the torch off and releases the camera. A stopped session cannot be restarted; create a new one.
  • Create a new VitalsSession for each reading screen. A completed or failed session does not restart. The one exception is a cancel with autoRestartAfterCancel = false: the session goes back to idle with the camera off and start() works again.
  • The reading needs the app in the foreground. Backgrounding, a phone call, another app taking the camera or a media-services reset while positioning or measuring ends the session with .failed(.interrupted(reason:)) (for backgrounding the reason is “The app moved to the background”); recreate the session when the user returns. Lock the reading screen’s orientation while a session runs.
  • Keep the phone still and the screen on for the duration. Consider UIApplication.shared.isIdleTimerDisabled while a session is running.
  • For debugging, VitalsSDK.isLoggingEnabled = true logs state transitions to os.Logger (subsystem com.neurofit.vitals); it is off by default and never logs values or frames.

Running the package tests

The package’s unit tests run on the Simulator (no camera needed). Pick the simulator by id, because a name such as iPhone 15 can match several installed runtimes:

Next steps