Hướng dẫn tích hợp Flash Liveness SDK cho iOS
Phiên bản SDK hiện tại: 1.4.8.
Tài liệu này dành cho ứng dụng iOS native tích hợp SDK qua CocoaPods.
1. Yêu cầu
| Hạng mục | Yêu cầu |
|---|---|
| iOS | 13.0 trở lên |
| Xcode | 14+; khuyến nghị dùng Xcode mới theo môi trường build của đối tác |
| CocoaPods | 1.10+ |
| Thiết bị test | iPhone/iPad thật |
| Package access | Có quyền truy cập repo/private pod FlashLiveness và dependency nội bộ như SignManager |
Framework hiện tại đóng gói slice ios-arm64; simulator không phải môi trường test chính. Nếu build simulator lỗi architecture, chuyển scheme sang device thật.
2. Cấu hình CocoaPods
Thêm SDK vào Podfile của app:
platform :ios, '13.0'
use_frameworks! :linkage => :static
target 'YourAppTarget' do
pod 'FlashLiveness',
:git => 'https://GITHUB_TOKEN@github.com/eidasvn/flash-liveness-ios.git',
:tag => '1.4.8'
end
FlashLiveness.podspec đã khai báo các dependency bắt buộc:
s.dependency 'SignManager'
s.dependency 'ObjectMapper', '~> 4.2'
s.dependency 'KeychainSwift', '~> 19.0'
Sau đó chạy:
pod install
Mở file .xcworkspace sau khi install pod. Không mở .xcodeproj khi project dùng CocoaPods.
3. Quyền camera
Thêm NSCameraUsageDescription vào Info.plist:
<key>NSCameraUsageDescription</key>
<string>Ứng dụng cần quyền camera để xác thực liveness.</string>
Trước khi start SDK, app nên kiểm tra quyền camera:
import AVFoundation
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
// Start liveness.
} else {
// Hướng dẫn người dùng mở camera permission trong Settings.
}
}
}
4. Khởi tạo SDK
Khởi tạo SDK một lần trước khi dùng API liveness:
import FlashLiveness
Networking.shared.setup(
appId: "YOUR_APP_ID",
logLevel: .debug,
url: "https://your-api-domain.com",
publicKey: "YOUR_RSA_PUBLIC_KEY",
privateKey: "YOUR_RSA_PRIVATE_KEY"
)
| Tham số | Ý nghĩa |
|---|---|
appId | Mã ứng dụng/đối tác được backend cấp |
url | Base URL API liveness |
publicKey | Public key dùng cho mã hóa/xác thực request |
privateKey | Private key dùng cho ký request |
logLevel | Mức log của SDK; production nên dùng .none hoặc .error |
5. Tạo màn hình liveness
Tạo một UIView làm vùng preview camera. View này cần có kích thước ổn định trước khi gọi createLivenessDetector.
final class LivenessViewController: UIViewController {
@IBOutlet private weak var previewView: UIView!
private var detector: LivenessUtilityDetector?
override func viewDidLoad() {
super.viewDidLoad()
setupDetector()
}
}
Giữ strong reference tới detector trong UIViewController; nếu detector bị release, session có thể không start hoặc callback không về.
6. Tạo detector
Ví dụ đầy đủ với tùy biến UI:
private func setupDetector() {
detector = FlashLivenessUtils.createLivenessDetector(
previewView: previewView,
mode: .online,
threshold: .high,
delegate: self,
instructionTextAttributes: [
.font: UIFont.systemFont(ofSize: 18, weight: .semibold),
.foregroundColor: UIColor.black
],
brightnessEnable: true,
faceMatchNoteDisplayMode: .showOnce,
faceMatchMaskConfig: FaceMatchMaskConfig(
maskBackgroundColor: "#66000000",
ovalStrokeColor: "#00FF88",
textBackgroundColor: "#FFFFFF",
textColor: "#1F2D5C",
instructionMessageMap: [
1: "Đưa mặt vào giữa khung hình"
],
instructionTopInset: 120
),
faceMatchNoteStyleConfig: FaceMatchNoteStyleConfig(
textColor: "#1F2D5C",
buttonTextColor: "#FFFFFF",
buttonBackgroundColor: "#458FF5",
titleText: "Lưu ý trước khi xác thực",
messageText: "Đưa điện thoại ngang tầm mắt và đặt khuôn mặt vào giữa khung.",
environmentTitleText: "Ánh sáng",
environmentDescriptionText: "Đầy đủ",
sunglassesTitleText: "Không",
sunglassesDescriptionText: "Đeo kính râm",
maskTitleText: "Không",
maskDescriptionText: "Đeo khẩu trang",
buttonTitleText: "Tôi đã hiểu"
),
imageResizeConfig: ImageResizeConfig(
maxDimension: 1024,
compression: 95
),
additionParam: [:],
additionHeader: [:]
)
}
Nếu không cần customize:
private func setupDetector() {
detector = FlashLivenessUtils.createLivenessDetector(
previewView: previewView,
mode: .online,
threshold: .high,
delegate: self,
brightnessEnable: true,
faceMatchNoteDisplayMode: .showOnce,
faceMatchMaskConfig: nil,
faceMatchNoteStyleConfig: nil,
imageResizeConfig: ImageResizeConfig(),
additionParam: [:],
additionHeader: [:]
)
}
7. Ý nghĩa cấu hình UI
| Cấu hình | Mục đích |
|---|---|
FaceMatchMaskConfig | Tùy biến mask/oval, text hướng dẫn và instructionTopInset trong màn camera |
FaceMatchNoteStyleConfig | Tùy biến bottom sheet hướng dẫn trước khi scan |
instructionTextAttributes | Font/màu instruction text |
additionParam | Param tùy biến gửi kèm request backend |
additionHeader | Header tùy biến gửi kèm request backend |
faceMatchNoteDisplayMode:
| Giá trị | Ý nghĩa |
|---|---|
.hidden | Không hiển thị hướng dẫn |
.showOnce | Hiển thị một lần trong app session |
.always | Luôn hiển thị mỗi lần vào luồng liveness |
ImageResizeConfig áp dụng cho ảnh gửi verify API và ảnh SDK trả về app.
| Thuộc tính | Kiểu | Ghi chú |
|---|---|---|
maxDimension | CGFloat | Cạnh dài nhất sau resize, hợp lệ 1...4096 |
compression | Int | Chất lượng JPEG, hợp lệ 0...100 |
Giá trị mặc định là maxDimension = 1024, compression = 95.
8. Bắt đầu session
Gọi initTransaction, sau đó dùng transaction.data để start session:
func startLiveness() {
Task {
do {
let transaction = try await Networking.shared.initTransaction()
try detector?.getVerificationRequiresAndStartSession(
transactionId: transaction.data
)
} catch {
print("Start liveness error: \(error)")
}
}
}
Nếu backend yêu cầu thêm tham số/header:
let transaction = try await Networking.shared.initTransaction(
additionParam: [
"customerId": "CUSTOMER_ID"
],
paramHeader: [
"X-Request-Source": "ios"
],
clientTransactionId: "CLIENT_TRANSACTION_ID"
)
9. Dừng session
Dừng camera khi màn hình biến mất hoặc khi không còn cần session:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
detector?.stopLiveness()
}
deinit {
detector?.stopLiveness()
}
10. Nhận kết quả
Implement LivenessUtilityDetectorDelegate:
extension LivenessViewController: LivenessUtilityDetectorDelegate {
func liveness(_ liveness: LivenessUtilityDetector, didFail withError: LivenessError) {
print("Liveness failed: \(withError)")
}
func liveness(_ liveness: LivenessUtilityDetector, didFinishWithResult result: LivenessResult) {
print("status: \(result.status)")
print("success: \(result.success)")
print("request_id: \(result.request_id)")
print("message: \(result.mess)")
}
func liveness(_ liveness: LivenessUtilityDetector, didFinishWithFaceImages images: LivenessFaceImages) {
print("offline images count: \(images.images?.count ?? 0)")
}
}
Các field thường dùng trong LivenessResult:
| Field | Ý nghĩa |
|---|---|
status | Mã trạng thái trả về từ API |
success | Kết quả liveness thành công/thất bại |
request_id | Mã request từ backend |
mess | Thông điệp kết quả |
livenesScore | Điểm liveness |
faceMatchingScore | Điểm face matching nếu backend trả về |
rawPayload | Payload gốc từ backend |
11. Register face tùy chọn
func registerFace(_ image: UIImage) {
Task {
do {
let response = try await Networking.shared.registerFace(faceImage: image)
print("Register status: \(response.status)")
print("Register message: \(response.message)")
} catch {
print("Register face error: \(error)")
}
}
}
Nếu backend yêu cầu thêm param/header:
let response = try await Networking.shared.registerFace(
faceImage: image,
additionParam: [
"customerId": "CUSTOMER_ID"
],
paramHeader: [
"X-Request-Source": "ios"
]
)
12. Checklist trước production
- Dùng device thật để test end-to-end.
- Kiểm tra quyền camera và flow user deny permission.
- Đảm bảo
detectorkhông bị release sớm. - Tắt debug log hoặc giảm
logLevel. - Kiểm tra credential CocoaPods/private repo không nằm trong source public.