跳轉到主要內容

iOS SDK 集成指南

文檔對應版本。iOS SDK 1.0.11。本頁依據更新日期爲 2026 年 9 月 11 日的 WIDTH 集成指南。部署前請確認所選 SDK 構建版本及其目標環境。

概述

Width Identity SDK 爲 iOS 應用提供即插即用的身份驗證流程。支持 KYC 和 KYB 工作流。你 import WidthIdentity 並調用一個入口;SDK 即呈現完整的採集 / 活體 / 審覈界面,並返回單一結果。公共 API 使用 Swift 原生類型(帶關聯值的枚舉、結構體、閉包),從 Swift 中使用。

系統要求

要求 最低版本
iOS 16.0+(部署目標)
Xcode 16+
依賴管理器 Swift Package Manager 或 CocoaPods

安裝

方式 A:Swift Package Manager(推薦)

WidthIdentity-iOS 是私有倉庫 — 在你的 GitHub 賬號拿到訪問權之前,Xcode 和 git 都只會提示倉庫不存在,所以先確認訪問權已開通,再執行下面的步驟。

在 Xcode 中:File > Add Package Dependencies…,輸入:

https://github.com/width-com/WidthIdentity-iOS.git

或在 Package.swift 中:

dependencies: [
    .package(url: "https://github.com/width-com/WidthIdentity-iOS.git", from: "1.0.11"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "WidthIdentity", package: "WidthIdentity-iOS"),
    ]),
]

方式 B:CocoaPods

WidthIdentity 發佈在 Width 的私有 Specs 倉庫,因此需在 Podfile 中聲明該源:

該 Specs 倉庫同樣是私有的 — 只有你的 GitHub 賬號拿到訪問權之後,pod install 才解析得到;沒有訪問權時,這個源看起來就是「倉庫不存在」。

source 'https://github.com/width-com/Specs.git'
source 'https://cdn.cocoapods.org/'   # default trunk, for any public pods

target 'YourApp' do
  pod 'WidthIdentity', '~> 1.0.11'
end

然後執行 pod install。

Info.plist 權限

SDK token 不再存放於 Info.plist。它在運行時通過 CredentialsConfig.sdkToken 傳入(見快速開始)。相機、麥克風、語音識別三項使用描述都必須聲明;照片庫那一項可選:

<!-- Required: document capture and liveness -->
<key>NSCameraUsageDescription</key>
<string>Used to capture identity documents and complete the liveness check.</string>

<!-- Required: document verification records background audio (anti-fraud) -->
<key>NSMicrophoneUsageDescription</key>
<string>Used to record background audio during document verification to help prevent fraud.</string>

<!-- Optional: only if you let users pick a document image from the photo
     library. The SDK uses the system PhotosPicker, which does not strictly
     require this key. -->
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to select identity document images.</string>

<!-- Required by App Store ingestion, never requested at runtime (see below) -->
<key>NSSpeechRecognitionUsageDescription</key>
<string>This app does not request speech recognition.</string>

NSCameraUsageDescription 與 NSMicrophoneUsageDescription 兩項都是必填:證件掃描環節會錄製背景音頻用於反欺詐,缺了麥克風這個 key,應用會在運行時直接崩潰。SDK 沒有任何與環境相關的 Info.plist key。

NSSpeechRecognitionUsageDescription 必填的原因不一樣。SDK 運行時從不申請語音識別,但 AwsLiveness.xcframework 靜態吸收了 Amplify 的 Predictions 插件,該插件引用了 SFSpeechRecognizer。缺這個 key,App Store 會在入庫(ingestion,上傳包進入審覈前的自動校驗)階段直接退回(錯誤碼 ITMS-90683)— 而且是在整個二進制包傳完之後才報,與這個接口有沒有被調用無關。這個 key 的文案直說「本應用不使用語音識別」即可:權限永不申請,用戶也就永遠看不到這句話。

打包構成

SDK 一共包含八個 xcframework:WidthIdentity、SecCamera、onnxruntime、AwsLiveness、Veriff、DocumentReader、DocumentReaderCore、RegulaCommon,全部自動嵌入。其中 AwsLiveness 是自包含的,你不需要自己引入任何 Amplify 或 AWS 依賴。

快速開始

import WidthIdentity 提供兩種等價的入口風格 — 按喜好選擇即可。兩者驅動完全相同的驗證界面。

直接輸入(UIKit)

import UIKit
import WidthIdentity

final class ViewController: UIViewController {
    func startVerification() throws {
        let input = try VerificationInput(
            credentials: CredentialsConfig(
                domain:   "YOUR_DOMAIN",
                sdkToken: "YOUR_SDK_TOKEN"
            ),
            customer: CustomerConfig(
                // Both customerId and email are required and must be non-empty.
                customerId: "user-123",
                email:      "user@example.com"
            )
        )

        WIDTHVerification.present(from: self, input: input) { result in
            switch result {
            case .completed(let id, let decision):  // .approved / .rejected / .review
                print("done:", id, decision)
            case .waiting(let id):                  print("waiting:", id)
            case .dismissed(let id):                print("dismissed:", id ?? "(no execution)")
            case .cancelled(let id):                print("cancelled:", id)
            case .failed(let code, let message, _): print("failed:", code, message)
            }
        }
    }
}

直接輸入(SwiftUI)

import SwiftUI
import WidthIdentity

struct ContentView: View {
    @State private var presenting = false
    @State private var customerId = ""
    @State private var email = ""

    var body: some View {
        Button("Verify") { presenting = true }
            .widthVerification(isPresented: $presenting, input: {
                // Lazy closure — built only when the cover is about to present.
                try VerificationInput(
                    credentials: CredentialsConfig(
                        domain: "YOUR_DOMAIN", sdkToken: "YOUR_SDK_TOKEN"
                    ),
                    customer: CustomerConfig(customerId: customerId, email: email)
                )
            }) { result in
                // If the input closure throws, you'll receive .failed(code: "W-INIT-410", ...) here.
            }
    }
}

Handler 風格入口(備選)

WIDTHMobileSDK 是另一種入口風格,以對象方式使用 — 先構造對象,設置屬性與各個 handler 閉包,再調用 present。

import WidthIdentity

let sdk = WIDTHMobileSDK(accessToken: "YOUR_SDK_TOKEN")   // RiskSec sdkToken
sdk.credentials = .init(domain: "YOUR_DOMAIN")   // appId/apiKey decoded from accessToken
sdk.applicant   = .init(customerId: "user-123", email: "user@example.com")
sdk.dayNightMode = .night                                 // optional; omit to follow the system
sdk.language     = .zh                                     // optional; .en (default) / .zh

sdk.verificationHandler { isApproved in
    print(isApproved ? "approved" : "finally rejected")
}
sdk.onStatusDidChange { sdk, _ in
    print("status:", sdk.status, "exec:", sdk.executionId ?? "-")
    if let fail = sdk.failReason { print("failed:", fail.code, fail.message) }
}
sdk.onEvent { _, event in /* INFO/WARN/ERROR lifecycle log */ }   // optional

sdk.present(from: self)

SwiftUI:

Button("Verify") { presenting = true }
    .widthMobileSDK(isPresented: $presenting) {
        let sdk = WIDTHMobileSDK(accessToken: "YOUR_SDK_TOKEN")
        sdk.credentials = .init(domain: "YOUR_DOMAIN")   // appId/apiKey decoded from accessToken
        sdk.applicant   = .init(customerId: customerId, email: email)
        sdk.verificationHandler { isApproved in /* ... */ }
        return sdk
    }
accessToken 裏裝的是 sdkToken;其餘憑據在客戶端通過 sdk.credentials / sdk.applicant 設置。憑據或申請人信息缺失這類配置錯誤,會表現爲 sdk.status == .failed,並在 sdk.failReason 中給出原因。

事件日誌(可選)

直接輸入入口接受一個 SdkEventLogger,可將 SDK 生命週期事件(INFO / WARN / ERROR)接入你自己的管道(Crashlytics、DataDog 等);回調觸發前已脫敏 PII(個人身份信息)。Handler 風格入口通過 sdk.onEvent 暴露相同的事件流。

final class MyLogger: SdkEventLogger {
    func onEvent(level: SdkLogLevel, tag: String, message: String, error: Error?) {
        // forward to your logging backend
    }
}

let input = try VerificationInput(credentials: ..., customer: ..., eventLogger: MyLogger())

配置參考

輸入結構與 Android 相同:VerificationInput、CredentialsConfig(domain 與 sdkToken)以及 CustomerConfig(customerId + email,兩者都必填)。字段詳情參見 Android 配置參考。在 iOS 上,VerificationInput 的初始化器是 throws 的,並額外接受一個可選的 eventLogger。

iOS 特定的入口與主題覆蓋:

符號 用途
WIDTHVerification.present(from:input:completion:) UIKit 原生入口
View.widthVerification(isPresented:input:onResult:) SwiftUI 原生入口
WIDTHMobileSDK(accessToken:) + credentials/applicant + present(from:) UIKit Handler 風格入口
View.widthMobileSDK(isPresented:makeSDK:) SwiftUI Handler 風格入口
ThemeConfig / ThemeMode 可選主題覆蓋
UILanguage 可選的界面語言(.en 爲默認值 / .zh)

結果處理

直接輸入的 VerificationResult

五種 Swift 枚舉變體(與 Android 的五種變體對應):

變體 何時
.completed(executionId, .approved) 服務端通過
.completed(executionId, .rejected) 服務端拒絕
.completed(executionId, .review) 提交在帶外進行人工審覈
.waiting(executionId) 等待服務端通知最終決定
.dismissed(executionId?) 用戶退出但打算稍後返回(可恢復);若工作流尚未開始,executionId 爲 nil
.cancelled(executionId) 用戶主動放棄;執行已銷燬,不可恢復
.failed(code, message, executionId?) 內部錯誤
Dismissed 與 Cancelled 的區別 .dismissed 表示用戶暫停、可恢復;.cancelled 表示用戶放棄、服務端執行已銷燬(不可恢復)。若失敗發生在執行創建之前,executionId 可能爲 nil;如果存在,請在工單中附上該 ID 以加快排查。

Handler 風格入口

verificationHandler { (isApproved: Bool) } 在最終通過/拒絕決定時觸發。sdk.status(WIDTHStatus):.approved、.finallyRejected、.pending(人工審覈)、.incomplete(中途退出 / 等待,可恢復)、.cancelled(已銷燬)、.failed。當爲 .failed 時,sdk.failReason(WIDTHException)爲 .initialization、.network、.api、.permission、.workflow、.unknown 之一 — 每個都暴露 code 與 message。

主題定製

驗證界面默認跟隨系統外觀。用 dayNightMode 強制指定模式;省略則跟隨系統。

let sdk = WIDTHMobileSDK(accessToken: "YOUR_SDK_TOKEN")
sdk.dayNightMode = .night   // optional; omit to follow the system

恢復與生命週期

與 Android 行爲一致。在 iOS 上,活躍工作流期間會禁用 sheet 的下滑關閉手勢,以防止意外丟失進度。

用戶選擇 行爲 結果 可恢復?
暫停並退出(保留進度) 服務端執行被保留 .dismissed 是
結束驗證(放棄) 服務端取消,執行已銷燬 .cancelled 否

恢復驗證時,使用相同的 customerId 再次啓動驗證界面,SDK 會自動檢測進行中的工作流。

權限

權限 Info.plist 鍵 必填 用途
相機 NSCameraUsageDescription 是 證件採集、活體檢測
麥克風 NSMicrophoneUsageDescription 是 證件驗證流程錄製背景音頻作爲反欺詐措施
照片庫 NSPhotoLibraryUsageDescription 否 僅當允許用戶從照片庫選取證件圖片時(系統 PhotosPicker 並不嚴格要求該 key)
語音識別 NSSpeechRecognitionUsageDescription 是 由 App Store 入庫校驗檢查。運行時永不申請該權限,用戶也就永遠看不到這句文案
相機與麥克風均爲必填。證件掃描步驟會錄製背景音頻作爲反欺詐措施,因此缺少 NSMicrophoneUsageDescription 會在啓動時導致硬崩潰。NSSpeechRecognitionUsageDescription 同樣必填,但它卡的是 App Store 入庫校驗、不是運行時(見「安裝」一節)。沒有任何 SDK 環境相關的 Info.plist key(無位置 key、無 token key)。網絡:通過 URLSession 使用 HTTPS,無需配置 NSAppTransportSecurity 例外。

App Thinning:XCFramework 支持 App Thinning。不嵌入 Bitcode(已在 Xcode 14 中棄用)。如遇 release 構建問題,請檢查應用 target 中 Strip Swift Symbols (STRIP_SWIFT_SYMBOLS) 是否設置爲默認值 (YES)。

故障排查

問題 解決方案
No such module 'WidthIdentity' SPM:File > Packages > Resolve Package Versions。確認 WidthIdentity 庫已添加到 Target > General > Frameworks, Libraries, and Embedded Content 中。CocoaPods:聲明私有源 source 'https://github.com/width-com/Specs.git',執行 pod install,並打開 .xcworkspace(而非 .xcodeproj)。
啓動時返回 .failed(如 W-INIT-410) VerificationInput 初始化器在 domain / sdkToken 爲空,或 customerId / email 爲空時拋錯;輸入閉包拋出的錯誤會表現爲 .failed。請確認這四項都非空。
攝像頭無法使用 確保 Info.plist 中存在 NSCameraUsageDescription 且描述不爲空。若用戶此前已拒絕權限,iOS 不會重新彈窗 — SDK 會檢測到此狀態並顯示引導界面,指引用戶前往設置 > 你的應用 > 相機開啓權限。
主題顏色未生效 檢查十六進制值、令牌名稱(區分大小寫)及正確的主題字典
SwiftUI sheet 未彈出 將 .widthVerification / .widthMobileSDK 附加到視圖層級中的視圖上,而非條件性添加
UIKit:"not in window hierarchy" 在 viewDidAppear 之後從視圖控制器發起呈現
活體檢測頁面語言不對 AWS 活體檢測頁面的文案跟隨設備語言,不跟隨 SDK 的 language 設置。流程的其餘部分仍按 sdk.language 顯示。
升級:把版本約束改成新版本號(SPM 的 from: 值,或 Podfile 裏的版本),再重新解析依賴即可。

集成說明

請按上文獲取軟件包訪問權限並完成安裝。SDK 構建版本及所用憑證須與目標 WIDTH 環境保持一致。