Chuyển tới nội dung chính

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ụcYêu cầu
iOS13.0 trở lên
Xcode14+; khuyến nghị dùng Xcode mới theo môi trường build của đối tác
CocoaPods1.10+
Thiết bị testiPhone/iPad thật
Package accessCó 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
appIdMã ứng dụng/đối tác được backend cấp
urlBase URL API liveness
publicKeyPublic key dùng cho mã hóa/xác thực request
privateKeyPrivate key dùng cho ký request
logLevelMứ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ìnhMục đích
FaceMatchMaskConfigTùy biến mask/oval, text hướng dẫn và instructionTopInset trong màn camera
FaceMatchNoteStyleConfigTùy biến bottom sheet hướng dẫn trước khi scan
instructionTextAttributesFont/màu instruction text
additionParamParam tùy biến gửi kèm request backend
additionHeaderHeader tùy biến gửi kèm request backend

faceMatchNoteDisplayMode:

Giá trịÝ nghĩa
.hiddenKhông hiển thị hướng dẫn
.showOnceHiển thị một lần trong app session
.alwaysLuô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ínhKiểuGhi chú
maxDimensionCGFloatCạnh dài nhất sau resize, hợp lệ 1...4096
compressionIntChấ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
statusMã trạng thái trả về từ API
successKết quả liveness thành công/thất bại
request_idMã request từ backend
messThông điệp kết quả
livenesScoreĐiểm liveness
faceMatchingScoreĐiểm face matching nếu backend trả về
rawPayloadPayload 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 detector khô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.