跳转到主要内容

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 环境保持一致。