Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/signing-sdk/face-auth-ios/llms.txt

Use this file to discover all available pages before exploring further.

Every call to TadSigningViewController completes through a single trailing closure that receives a TadSigningResult. The result is either a .success with a JWT and a request identifier, or a .failure with a typed error code and a human-readable message. Handling both cases correctly keeps your UI responsive and gives users actionable feedback when something goes wrong.

The TadSigningResult enum

TadSigningResult has two cases:
// Success — jwt is empty for .register, populated for .sign
case success(jwt: String, requestId: String)

// Failure — code is a typed enum, rawValue gives the string code
case failure(code: TadSigningErrorCode, message: String)
The closure is called on the main thread after the SDK dismisses its view controller, so you can update UI properties directly without dispatching.

Success handling

case .success(let jwt, let requestId):
    isLoading = false
    resultColor = .green
    result = mode == .register
        ? "✅ Registered\nrequestId: \(requestId)"
        : "✅ Signed\nrequestId: \(requestId)\n\nJWT:\n\(jwt)"
  • requestId — always present. Use it to correlate the SDK event with a record on your backend.
  • jwt — populated only for .sign mode. For .register it is an empty string. Send the JWT to your server for ES512 signature verification before trusting its claims.

Failure handling

case .failure(let code, let message):
    isLoading = false
    resultColor = .red
    result = "❌ \(code.rawValue)\n\(message)"
  • code — a typed TadSigningErrorCode enum value. Access the underlying string identifier with .rawValue for logging or display.
  • message — a human-readable description of the error, suitable for logging. Do not display raw error messages directly to end users; map them to localized, user-friendly strings in your UI layer.
Access the string representation of an error code with code.rawValue. The enum type gives you compile-time safety when switching over known cases, while .rawValue is useful for logging and analytics.

Complete result handler

The following is the complete switch statement from the demo app, showing both cases together:
let signingVC = TadSigningViewController(
    config:  SDKConfig.shared,
    bankId:  bankId,
    dto:     dto,
    mode:    mode
) { signingResult in
    isLoading = false
    switch signingResult {
    case .success(let jwt, let requestId):
        resultColor = .green
        result = mode == .register
            ? "✅ Registered\nrequestId: \(requestId)"
            : "✅ Signed\nrequestId: \(requestId)\n\nJWT:\n\(jwt)"
    case .failure(let code, let message):
        resultColor = .red
        result = "❌ \(code.rawValue)\n\(message)"
    }
}

Loading state management

Use a boolean state variable to track whether the SDK is active. Set it to true before presenting and back to false in the completion closure — in both the success and failure branches.
// Before presenting
isLoading = true

// Inside the completion closure (both branches)
isLoading = false
Disable action buttons while isLoading is true to prevent the user from triggering a second SDK session before the first one completes:
Button(action: { present(mode: .register) }) {
    Label("Register", systemImage: "person.badge.plus")
}
.disabled(isLoading || bankId.isEmpty)

Button(action: { present(mode: .sign) }) {
    Label("Sign In", systemImage: "person.badge.key.fill")
}
.disabled(isLoading || bankId.isEmpty)
The completion closure is called on the main thread, so you can update @State properties and @Published values directly without wrapping in DispatchQueue.main.async. Avoid performing heavy synchronous work (network calls, file I/O) inside the closure — dispatch those to a background queue to keep the UI responsive.

Best practices

  • Always handle both cases. A non-exhaustive switch will cause a compile error in Swift, but ensure your UI recovers gracefully from every failure path rather than silently ignoring errors.
  • Map error codes to localized messages. Use code.rawValue to look up user-facing strings in your localization table rather than surfacing SDK-internal error text directly.
  • Log failures with context. Include code.rawValue, message, the bankId (or a hashed version), and the mode in your logging pipeline to aid debugging.
  • Do not block the main thread. Keep the closure body lightweight; dispatch any network or persistence work off the main queue.

Build docs developers (and LLMs) love