iOS reference
Every public type in the FacePing Swift package: setup, enrolment, verification, the camera view and results.
Package: faceping/faceping-ios (Swift Package Manager, preview). Starter app: faceping/ios-starter. iOS 15.1 or later, Xcode 26 or later.
In Xcode: File → Add Package Dependencies…, enter https://github.com/faceping/faceping-ios and pick Exact Version 1.0.0-preview.1. Or in a Package.swift:
.package(url: "https://github.com/faceping/faceping-ios", exact: "1.0.0-preview.1")
Your app only needs import FacePing. Simulator builds need EXCLUDED_ARCHS[sdk=iphonesimulator*] = x86_64 (the package has Apple silicon simulator slices only), and the simulator has no front camera: test the live check on an iPhone.
Setup#
// Once, for example in your App's init. Returns at once and loads the models in the background.
try FacePing.configure(sandboxKey: "fp_test_…") // sandbox: faces enrolled on the device
// try FacePing.configure(deviceToken: "fpd_…") // production: faces synced from your list
// Anywhere later
let faceping = FacePing.shared // nil until it's set up
let faceping = try FacePing(sandboxKey: "fp_test_…") does the same and returns it. One FacePing runs per app: setting it up again with the same options gives you the same one (so it's safe inside a SwiftUI view); with different options it throws (call close() on the old one first).
FacePingOptions#
For every option, use FacePing.configure(FacePingOptions(...)) or FacePing(options:).
| Property | Type | Default | Notes |
|---|---|---|---|
sandboxKey |
String? |
nil |
A fp_test_ key from Developers → Sandbox keys. |
deviceToken |
String? |
nil |
Production. A fpd_ token for one check-in list. Set this or sandboxKey, not both. |
region |
FacePingRegion |
.eu |
Your account's region (sets the API address). |
apiBaseUrl |
URL? |
nil |
Overrides the region's address, for example for the UK or US region. |
passiveLiveness |
Bool |
true |
Passive liveness on every check. Turn it off only if your app runs its own liveness check first. |
autoSyncInterval |
TimeInterval? |
300 (seconds) | Production only. nil turns automatic sync off; call sync() yourself. |
gatePolicy |
GatePolicy |
.default |
How strict verification is. Change only if you know why. |
Setting neither sandboxKey nor deviceToken, or both, or a sandbox key that doesn't start with fp_test_, throws FacePingError.setup.
FacePing#
public final class FacePing {
public static func configure(sandboxKey: String, region: FacePingRegion = .eu) throws -> FacePing
public static func configure(deviceToken: String, region: FacePingRegion = .eu) throws -> FacePing
public static func configure(_ options: FacePingOptions) throws -> FacePing
public static var shared: FacePing? { get }
public static let sdkVersion: String
public var options: FacePingOptions { get }
public var isSandbox: Bool { get }
public var enrolledIds: Set<String> { get }
public var synced: AsyncStream<SyncResult> { get } // every sync, automatic or not
public func enrol(id: String, photo: FaceImage) async throws -> EnrolResult // also UIImage or Data
@MainActor
public func verifyLive(id: String, camera: FaceCameraView) async throws -> VerifyResult
public func verify(id: String, photo: FaceImage) async throws -> VerifyResult // also UIImage or Data
public func forget(id: String) async throws -> Bool
public func forgetAll() async throws
public func sync() async throws -> SyncResult
public func close()
}
| Method | Notes |
|---|---|
enrol |
Sandbox only. Production enrolment happens with consent on your server or through the FacePing widget. Replaces any face already stored under id. |
verifyLive |
Runs a live attempt on the camera's frames: match, head turn, match again. Shows prompts in the FaceCameraView and sets its guide. Times out after 10 seconds with .noFace or the last failure; cancel the task to stop early (it then throws CancellationError). |
verify |
One photo, passive liveness only. |
forget |
Sandbox: deletes the face stored under id from the device straight away, and returns false if there was none. Production: throws FacePingError.invalidOperation; remove a person through your server instead, and every device drops them at its next sync. |
forgetAll |
Deletes every face on the device straight away. In production the next sync brings back the people still enrolled. |
enrolledIds |
Who can be verified on this device, as of the last enrol, forget or sync. |
sync |
Production: downloads the latest faces for the device's list; a revoked device wipes its faces. Sandbox: activates the key, or renews its licence when due. |
close |
Stops automatic sync and frees the engine. shared becomes nil; set FacePing up again to start over. |
FaceImage#
What you can pass as a photo.
| From | Example |
|---|---|
UIImage |
try await faceping.enrol(id: "me", photo: image) |
Data (JPEG, PNG or HEIC) |
try await faceping.enrol(id: "me", photo: data) |
CGImage |
try await faceping.enrol(id: "me", photo: FaceImage(cgImage: image, orientation: .up)) |
FaceCameraView capture |
try await faceping.enrol(id: "me", photo: camera.capture()) |
Photos are turned upright and scaled down to 1280 pixels on the device before anything else happens.
FaceCameraView and FaceCamera#
FaceCameraView (UIKit) is a front-camera view with a face guide and the liveness prompts ("Turn your head left", "Look back at the camera"). The SDK runs the camera (AVFoundation), so you don't need other camera code. In SwiftUI, keep the view in your model and show it with FaceCamera:
@MainActor final class Model: ObservableObject {
let camera = FaceCameraView()
}
FaceCamera(model.camera)
.frame(height: 420)
| Member | Notes |
|---|---|
func capture() async throws -> FaceImage |
Takes a still from the live view: upright, as the camera sees it (the preview is mirrored, the picture isn't). Throws FacePingError.camera if the camera doesn't deliver a picture within 5 seconds. |
var showGuide: Bool |
The face outline. Default true. |
var prompt: String? |
Set by verifyLive while a live check runs. |
var guideState: FaceGuideState |
How the guide looks: .ready (dashed), .working (a scan line), .match (green) or .noMatch (red). verifyLive sets it; set it yourself for other steps, for example .working while enrolling. |
var onCameraFailed: ((String) -> Void)? |
Called on the main thread when the camera can't start: permission refused or no front camera. The message says which; cameraError keeps it. |
func startCamera() / stopCamera() |
Called for you when the view is added to or removed from a window. Call startCamera() if your app gets camera permission another way. |
static func requestCameraAccess() async -> Bool |
Asks for the camera now, if the person hasn't been asked yet, and returns their answer. |
The prompts come from the SDK's FacePing.strings. To translate or reword them, add a FacePing.strings table to your app with the keys faceping.prompt.lookAtCamera, faceping.prompt.turnLeft, faceping.prompt.turnRight and faceping.prompt.lookBack.
Permissions#
Add NSCameraUsageDescription to your app's Info.plist (in Xcode: the target's Info tab, Privacy - Camera Usage Description). FaceCameraView asks for the camera the first time it appears, if your app hasn't asked already. Without the key, iOS stops the app when the camera starts.
FacePing's data lives in Application Support/faceping and is excluded from backups: its key is in this device's Keychain and never leaves it.
Results#
public struct EnrolResult {
public let outcome: EnrolOutcome
public let id: String
public var isEnrolled: Bool { get } // outcome == .enrolled
}
public enum EnrolOutcome {
case enrolled, noFace, multipleFaces, faceTooSmall, notLive, limitReached
}
public struct VerifyResult {
public let outcome: VerifyOutcome
public let similarity: Double
public let livenessScore: Double?
public let elapsed: TimeInterval
public let isSandbox: Bool
public var isMatch: Bool { get } // outcome == .match
}
public enum VerifyOutcome {
case match, noMatch, notEnrolled, noFace, livenessFailed, challengeFailed, expired, leaseExpired
}
public struct SyncResult {
public let status: PackDownloadStatus // .ok, .gone, .revoked or .unavailable
public let enrolled: Int
public let leaseUntil: Date? // production: verifies offline until then
public let deleteAfter: Date?
}
limitReached means the sandbox already holds 25 faces on this device: forget one first. leaseExpired (production) means the device hasn't synced for longer than its lease, 24 hours by default; it needs a connection before it verifies again.
The package is built with library evolution, so its enums aren't frozen: add @unknown default when you switch over an outcome.
switch result.outcome {
case .match: openTheDoor()
case .noMatch, .livenessFailed: sendToStaff()
default: tryAgain() // or list every case, plus @unknown default
}
Errors#
Everything FacePing throws is a FacePingError, with a message ready to show.
| Case | When |
|---|---|
.setup |
The key or token is missing, invalid or revoked, or the sandbox key couldn't be activated (no connection on first run). |
.model |
The face models couldn't load on this device. Send people to your normal entry. |
.invalidOperation |
Not allowed in this mode or state: enrolling or forgetting one person in production, or a closed FacePing. |
.invalidArgument |
A bad argument: an empty id, or a photo that isn't a readable image. |
.storage |
The device's secure storage (Keychain or files) failed. |
.camera |
The camera couldn't start or didn't deliver a picture. |
Everything else is a result, not an exception: a face check never throws because someone looked away.