π― Learning Path:
Welcome to the RELID IDV Post-Login KYC Flow codelab! This tutorial teaches you how to implement comprehensive Know Your Customer (KYC) identity verification for activated customers using document scanning, facial recognition, and biometric authentication.
In this codelab, you'll implement a complete post-login KYC workflow for activated customers:
This codelab demonstrates post-login KYC for activated customers, where identity verification occurs after user login for compliance purposes:
Pre-Login IDV Flow (Challenge Mode 8):
Activation Code β IDV (Document β Selfie β Consent) β Password Setup β Dashboard
Post-Login KYC Flow (Challenge Mode 9 - This Codelab):
Login β Dashboard β Navigate to KYC β IDV (Document β Selfie β Consent) β Completion
Critical Differences:
Understanding the complete flow is crucial before implementation:
1. User Login (Activated Customer)
β (Existing credentials authenticated)
2. Dashboard β Open Side Menu
β (User navigates to KYC option)
3. π Activated Customer KYC Screen
ββ Display KYC information and requirements
ββ User clicks "Start KYC Verification"
ββ View controller-level event handler registered (persistent)
ββ initiateActivatedCustomerKYC("Post Login KYC")
β β’ workflow: 6 (RDNA_IDV_POSTLOGIN_KYC)
β β’ challengeMode: 9 (post-login)
ββ SDK initiates post-login KYC workflow
β
4. π IDV Document Scan Process
ββ getIDVDocumentScanProcessStartConfirmation event
β β’ idvWorkflow: 6 (RDNA_IDV_POSTLOGIN_KYC)
ββ setIDVDocumentScanProcessStartConfirmation(true, 6)
ββ Native Document Capture (Regula SDK)
ββ getIDVConfirmDocumentDetails event
β β’ challengeMode: 9
β β’ documentDetails: OCR-extracted document fields
β β’ Close button: cancel API (mode 9) β triggers onIDVActivatedCustomerKYCResponse
ββ setIDVConfirmDocumentDetails(true, 9)
β
5. π€³ IDV Selfie Capture Process
ββ getIDVSelfieProcessStartConfirmation event
β β’ idvWorkflow: 6 (RDNA_IDV_POSTLOGIN_KYC)
ββ setIDVSelfieProcessStartConfirmation(true, false, 6)
ββ Native Selfie Capture (Liveness Detection)
ββ getIDVSelfieConfirmation event
β β’ challengeMode: 9
β β’ selfieDetails: Biometric match results
β β’ Close button: navigate directly (mode 9) β no cancel API
ββ setIDVSelfieConfirmation("true", 9)
β
6. π IDV Biometric Consent
ββ getIDVBiometricOptInConsent event
β β’ challengeMode: 9
β β’ status at root level (no challengeResponse for mode 9)
β β’ Close button: navigate directly (mode 9)
ββ setIDVBiometricOptInConsent(true, 9)
β
7. β
KYC Completion
ββ onIDVActivatedCustomerKYCResponse event triggered
ββ View controller-level handler receives event (even if on different screen)
ββ Handler navigates back to ActivatedCustomerKYCViewController
β β’ navigationController?.popToViewController(_, animated: true)
ββ Dual validation performed:
β β’ Check error.longErrorCode (0 = OK, 240 = User Cancelled)
β β’ Check status.statusCode (100 or 0 = success)
ββ Success/Error alert shown
ββ User clicks "Go to Dashboard" β Navigate to Dashboard
For post-login KYC flow, you'll use these specific values:
Parameter | Value | Description |
idvWorkflow |
|
|
challengeMode |
| Post-login challenge mode |
Comparison with Pre-Login Flow:
Aspect | Pre-Login (Activation) | Post-Login (KYC) |
Workflow | 0-5 (Activation workflows) | 6 (RDNA_IDV_POSTLOGIN_KYC) |
Challenge Mode | 8 | 9 |
Entry Point | ActivationCodeViewController | ActivatedCustomerKYCViewController (side menu) |
Close Button (Document Confirm) | resetAuthState() | cancel API β triggers event |
Close Button (Selfie Confirm) | resetAuthState() | Direct navigation |
Close Button (Biometric Consent) | resetAuthState() | Direct navigation |
Response Structure | challengeResponse.status | status at root level |
Event Handler | Global delegate only | View controller-level + global delegate |
Ensure you have completed these requirements:
# Required tools
Xcode >= 14.0
Swift >= 5.5
CocoaPods (for dependency management)
Verify your Xcode installation:
xcode-select --version
The code to get started can be found in a GitHub repository.
You can clone the repository using the following command:
git clone https://github.com/uniken-team/relid-codelab-ios.git
Navigate to the relid-IDV-post-login-kyc folder in the repository you cloned earlier:
cd relid-codelab-ios/relid-IDV-post-login-kyc
The post-login KYC codelab follows this comprehensive structure:
relid-IDV-post-login-kyc/
βββ π± Complete Swift/iOS MFA + IDV App
β βββ relidcodelab.xcodeproj # Xcode project
β βββ relidcodelab.xcworkspace # CocoaPods workspace (USE THIS!)
β βββ Podfile # CocoaPods configuration
β βββ Pods/ # CocoaPods dependencies (auto-generated)
β β βββ DocumentReader/ # Regula document reader (via RELIDIDV pod)
β β βββ DocumentReaderFullAuthRFID/ # Regula RFID authentication (via RELIDIDV pod)
β β βββ onnxruntime-c/ # ONNX runtime for ML models (via RELIDIDV pod)
β β βββ IQKeyboardManagerSwift/ # Keyboard management
β β βββ SideMenu/ # Drawer navigation
β β βββ Firebase/ # Push notifications (FCM)
β βββ RELID.xcframework # π¦ REL-ID Native SDK (Main SDK - REQUIRED)
β β # - Core MFA authentication APIs
β β # - Base RDNA client functionality
β β # - Session management
β βββ RELID_IDV/ # π¦ REL-ID IDV Local Pod (IDV Components - REQUIRED)
β β βββ RELIDIDV.podspec # Pod specification
β β βββ AwFaceCapture.xcframework # Biometric selfie capture framework
β β # Dependencies (auto-installed via CocoaPods):
β β # - DocumentReader 8.2.4934 (Regula)
β β # - DocumentReaderFullAuthRFID 8.2.13463
β β # - onnxruntime-c (ML runtime)
β βββ relidcodelab/idv-artifacts/ # π IDV Assets Directory (REQUIRED)
β βββ db.dat # Regula document recognition database (105MB)
β βββ regula.license # Regula document reader license for iOS
β βββ Certificates.bundle/ # ICAO PKD certificates bundle
β βββ icaopkd-002-complete-000325.ldif # Document validation certificates
β
βββ π¦ IDV Post-Login KYC Source Architecture
β βββ relidcodelab/Sources/
β βββ Tutorial/ # Enhanced MFA + IDV flow
β β βββ Navigation/ # Enhanced navigation with IDV support
β β β βββ AppCoordinator.swift # Event-driven navigation coordinator
β β β βββ AppIDVCoordinator.swift # π IDV-specific coordinator
β β β # - IDV event routing
β β β # - Screen state management
β β β # - showActivatedCustomerKYC()
β β βββ Screens/ # Enhanced screens with IDV
β β βββ Components/ # Reusable UI components
β β β βββ UIColor+Extensions.swift
β β βββ MFA/ # π MFA screens (base authentication)
β β β βββ CheckUserViewController.swift # User validation
β β β βββ ActivationCodeViewController.swift # OTP verification
β β β βββ SetPasswordViewController.swift # Password creation
β β β βββ VerifyPasswordViewController.swift # Password verification
β β β βββ DashboardViewController.swift # Main dashboard
β β β βββ MenuViewController.swift # π Enhanced with KYC menu item
β β β βββ ... # Other MFA screens
β β βββ IDV/ # π IDV Post-Login KYC screens (6 screens)
β β βββ ActivatedCustomerKYCViewController.swift # π Post-Login KYC entry point
β β β # - KYC initiation screen
β β β # - Persistent event handling
β β βββ IDVDocumentProcessStartConfirmationViewController.swift # Document scan initiation
β β βββ IDVConfirmDocumentDetailsViewController.swift # π Enhanced for mode 9
β β βββ IDVSelfieProcessStartConfirmationViewController.swift # Selfie capture initiation
β β βββ IDVSelfieConfirmationViewController.swift # π Enhanced for mode 9
β β βββ IDVBiometricOptInConsentViewController.swift # π Enhanced for mode 9
β βββ Uniken/ # π‘οΈ Enhanced REL-ID Integration
β βββ Services/ # π Enhanced SDK service layer
β β βββ RDNAService.swift # Base MFA service layer
β β βββ RDNADelegateManager.swift # Base MFA delegate callbacks
β β βββ IDV/ # π IDV service layer
β β βββ RDNAIDVService.swift # Complete IDV API methods
β β β # - π initiateActivatedCustomerKYC()
β β β # - setIDVDocumentScanProcessStartConfirmation()
β β β # - setIDVConfirmDocumentDetails()
β β β # - setIDVSelfieProcessStartConfirmation()
β β β # - setIDVSelfieConfirmation()
β β β # - setIDVBiometricOptInConsent()
β β βββ RDNAIDVDelegateManager.swift # Complete IDV delegate callbacks
β β # - getIDVDocumentScanProcessStartConfirmation
β β # - getIDVConfirmDocumentDetails
β β # - getIDVSelfieProcessStartConfirmation
β β # - getIDVSelfieConfirmation
β β # - getIDVBiometricOptInConsent
β β # - π onIDVActivatedCustomerKYCResponse
β βββ Utils/ # Helper utilities
β β βββ ConnectionProfileParser.swift # Profile configuration
β βββ CP/ # Connection Profile
β βββ agent_info.json
β
βββ π Project Configuration
βββ Podfile # CocoaPods dependencies (RELID SDK, Regula, IQKeyboardManager)
βββ Info.plist # Camera permissions configuration
βββ Base.lproj/
βββ Main.storyboard # π Complete UI with all MFA + IDV + KYC scenes
βββ LaunchScreen.storyboard
# Navigate to the codelab folder
cd relid-codelab-ios/relid-IDV-post-login-kyc
# Add the `RELID.xcframework` to your project:
# - Drag and drop `RELID.xcframework` into your Xcode project
# - Select your target β General β Frameworks, Libraries, and Embedded Content
# - Set embed mode to "Embed & Sign"
# Place the RELIDIDV local pod (IDV libs) at ./RELID_IDV/
# (Contact REL-ID team for the framework)
# Install CocoaPods (if not already installed)
sudo gem install cocoapods
# Install dependencies
pod install
The following IDV assets need to be configured in your project at the specified paths:
iOS Configuration:
Place the following files (provided by Uniken admin team) at these locations:
relidcodelab/idv-artifacts/db.datrelidcodelab/idv-artifacts/regula.licenserelidcodelab/idv-artifacts/Certificates.bundle/icaopkd-002-complete-000325.ldifAfter adding IDV assets to your project:
# Verify IDV assets are present
ls relidcodelab/idv-artifacts/
# Should show: db.dat, regula.license, Certificates.bundle/
# Check file sizes
du -h relidcodelab/idv-artifacts/db.dat
# Should show: ~105M
In Xcode:
relidcodelabInfo.plist:<!-- Camera Access -->
<key>NSCameraUsageDescription</key>
<string>Camera access is required for document scanning and selfie capture</string>
<!-- NFC Access (for ePassport scanning) -->
<key>NFCReaderUsageDescription</key>
<string>NFC access for reading electronic passport data</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
# Open the workspace (NOT the .xcodeproj!)
open relidcodelab.xcworkspace
In Xcode:
iOS SDKs are distributed as frameworks (.framework or .xcframework) that link with your application.
RELID iOS SDK Architecture:
The RELID iOS SDK uses a delegate pattern for event callbacks:
import UIKit
import RELID
class ActivatedCustomerKYCViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Register event handler
setupEventHandlers()
}
private func setupEventHandlers() {
// Register persistent event handler for KYC completion
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = { [weak self] userID, status, error in
self?.handleKYCCompletion(userID: userID, status: status, error: error)
}
}
@IBAction func startKYCButtonTapped(_ sender: UIButton) {
// Initiate KYC workflow
let error = RDNAIDVService.shared.initiateActivatedCustomerKYC(reason: "Post Login KYC")
if error.longErrorCode == 0 {
print("KYC initiated successfully")
// SDK will auto-trigger IDV workflow events
} else {
print("Failed to initiate KYC: \\(error.errorString ?? "")")
}
}
private func handleKYCCompletion(userID: String, status: RDNARequestStatus, error: RDNAError) {
// Dual validation pattern
let errorCode = error.longErrorCode
let statusCode = status.statusCode
if errorCode == 0 && (statusCode == 100 || statusCode == 0) {
// Success
showSuccessAlert()
} else {
// Error
showErrorAlert(message: error.errorString ?? "KYC verification failed")
}
}
}
RDNAIDVService:
RDNAIDVDelegateManager:
View Controllers:
The RDNAIDVService class provides a singleton service for Identity Verification (IDV) related SDK operations. It wraps the native RELID SDK IDV API with type-safe interfaces.
The service includes the initiateActivatedCustomerKYC() method which initiates the post-login KYC workflow:
// Sources/Uniken/Services/IDV/RDNAIDVService.swift
import Foundation
import RELID
/**
* REL-ID IDV SDK Service
*
* Provides a singleton service for Identity Verification (IDV) related SDK operations.
* Wraps the native RELID SDK IDV API with type-safe interface.
* Handles document scanning, selfie capture, and biometric consent operations.
*
* IDV APIs:
*
* **Document Scan APIs:**
* - setIDVDocumentScanProcessStartConfirmation: Start/cancel document scan
* - setIDVConfirmDocumentDetails: Confirm/reject scanned document OCR data
*
* **Selfie Capture APIs:**
* - setIDVSelfieProcessStartConfirmation: Start/cancel selfie capture
* - setIDVSelfieConfirmation: Confirm/reject selfie biometric results
*
* **Biometric Consent APIs:**
* - setIDVBiometricOptInConsent: User consent for biometric template storage
*
* **KYC APIs:**
* - initiateActivatedCustomerKYC: Initiate post-login KYC workflow
*/
class RDNAIDVService {
// MARK: - Singleton
static let shared = RDNAIDVService()
private let rdna = RDNA.sharedInstance()
private init() {
print("RDNAIDVService - Service initialized")
}
// MARK: - KYC APIs
/**
* Initiates activated customer KYC workflow (Post-Login)
*
* This API initiates the KYC (Know Your Customer) process for an already activated customer
* who needs to complete identity verification post-login.
*
* **Workflow:**
* 1. User must be logged in (user session established)
* 2. Call this API to start post-login KYC
* 3. SDK automatically triggers IDV workflow events:
* - getIDVDocumentScanProcessStartConfirmation (workflow: IDV_POSTLOGIN_KYC = 6)
* - getIDVConfirmDocumentDetails (challengeMode: RDNA_IDV_API_BIOMETRIC_AND_DOCUMENT_SCAN = 9)
* - getIDVSelfieProcessStartConfirmation (workflow: IDV_POSTLOGIN_KYC = 6)
* - getIDVSelfieConfirmation (challengeMode: RDNA_IDV_API_BIOMETRIC_AND_DOCUMENT_SCAN = 9)
* - getIDVBiometricOptInConsent (challengeMode: RDNA_IDV_API_BIOMETRIC_AND_DOCUMENT_SCAN = 9)
* 4. Finally triggers: onIDVActivatedCustomerKYCResponse
*
* **Response Validation:**
* 1. Check error.longErrorCode immediately (synchronous error check)
* 2. Wait for onIDVActivatedCustomerKYCResponse callback for final result
* 3. In callback, validate BOTH:
* - error.longErrorCode (SDK error)
* - status.statusCode (backend error)
* 4. Success only if: error.longErrorCode == 0 AND (statusCode == 100 OR statusCode == 0)
*
* - Parameter reason: Reason for initiating KYC (e.g., "Post Login KYC")
* - Returns: RDNAError indicating immediate success or failure
*/
func initiateActivatedCustomerKYC(reason: String) -> RDNAError {
print("RDNAIDVService - Initiating activated customer KYC: reason=\\(reason)")
let error = rdna.initiateActivatedCustomerKYC(reason)
if error.longErrorCode == 0 {
print("RDNAIDVService - Activated customer KYC initiated successfully")
print("RDNAIDVService - SDK will auto-trigger IDV workflow events (workflow=6, challengeMode=9)")
} else {
print("RDNAIDVService - Failed to initiate activated customer KYC: \\(error.longErrorCode) - \\(error.errorString ?? "")")
}
return error
}
// MARK: - Document Scan APIs
func setIDVDocumentScanProcessStartConfirmation(
isConfirm: Bool,
idvWorkflow: RDNAIDVWorkflow
) -> RDNAError {
// Implementation as shown in previous sections
return rdna.setIDVDocumentScanProcessStartConfirmation(isConfirm, idvWorkflow: idvWorkflow)
}
func setIDVConfirmDocumentDetails(
isConfirm: Bool,
challengeMode: RDNAChallengeOpMode
) -> RDNAError {
// Implementation as shown in previous sections
return rdna.setIDVConfirmDocumentDetails(isConfirm, challenge: challengeMode)
}
// MARK: - Selfie Capture APIs
func setIDVSelfieProcessStartConfirmation(
isConfirm: Bool,
useDeviceBackCamera: Bool,
idvWorkflow: RDNAIDVWorkflow
) -> RDNAError {
// Implementation as shown in previous sections
return rdna.setIDVSelfieProcessStartConfirmation(isConfirm, useDeviceBackCamera: useDeviceBackCamera, idvWorkflow: idvWorkflow)
}
func setIDVSelfieConfirmation(
action: String,
challengeMode: RDNAChallengeOpMode
) -> RDNAError {
// Implementation as shown in previous sections
return rdna.setIDVSelfieConfirmation(action, challenge: challengeMode)
}
// MARK: - Biometric Consent APIs
func setIDVBiometricOptInConsent(
isOptIn: Bool,
challengeMode: RDNAChallengeOpMode
) -> RDNAError {
// Implementation as shown in previous sections
return rdna.setIDVBiometricOptInConsent(isOptIn, challenge: challengeMode)
}
}
RDNA_IDV_POSTLOGIN_KYC workflowonIDVActivatedCustomerKYCResponse delegate callbacklongErrorCode for synchronous errorsThe RDNAIDVDelegateManager class manages IDV-specific SDK callbacks. It implements IDV protocol methods from RELID SDK and dispatches to closures.
The delegate manager includes the onIDVActivatedCustomerKYCResponse closure property:
// Sources/Uniken/Services/IDV/RDNAIDVDelegateManager.swift
import Foundation
import RELID
/**
* RDNAIDVDelegateManager - Manages IDV-specific SDK callbacks
*
* Implements IDV protocol methods from RELID SDK and dispatches to closures.
* Handles document scanning, selfie capture, and biometric consent callbacks.
*
* **IDV Protocols Implemented (Modular):**
* - RDNAIDVDocumentProcessingCallbacks (6 methods)
* - RDNAIDVSelfieProcessingCallbacks (2 methods)
* - RDNAIDVBiometricManagementCallbacks (10 methods)
* - RDNAIDVKYCManagementCallbacks (6 methods)
*/
class RDNAIDVDelegateManager: NSObject,
RDNAIDVDocumentProcessingCallbacks,
RDNAIDVSelfieProcessingCallbacks,
RDNAIDVBiometricManagementCallbacks,
RDNAIDVKYCManagementCallbacks {
// MARK: - Singleton
static let shared = RDNAIDVDelegateManager()
private override init() {
super.init()
print("RDNAIDVDelegateManager - Initialized")
}
// MARK: - KYC Callback Closures
/// Closure invoked when activated customer KYC process completes
/// IDV-SPECIFIC (POST-LOGIN): Navigate back to ActivatedCustomerKYCViewController
/// This callback fires after completing the entire IDV workflow (document scan, selfie, biometric consent)
/// - Parameters:
/// - userID: User ID who completed KYC
/// - status: Status with statusCode (100=success, other=error)
/// - error: Error information (check longErrorCode)
var onIDVActivatedCustomerKYCResponse: ((_ userID: String, _ status: RDNARequestStatus, _ error: RDNAError) -> Void)?
// MARK: - Document Scan Callback Closures
var onGetIDVDocumentScanProcessStartConfirmation: ((_ idvWorkflow: RDNAIDVWorkflow) -> Void)?
var onGetIDVConfirmDocumentDetails: ((_ challengeMode: RDNAChallengeOpMode, _ responseDataJSON: String) -> Void)?
// MARK: - Selfie Capture Callback Closures
var onGetIDVSelfieProcessStartConfirmation: ((_ idvWorkflow: RDNAIDVWorkflow) -> Void)?
var onGetIDVSelfieConfirmation: ((_ userID: String, _ challengeMode: RDNAChallengeOpMode, _ responseDataJSON: String) -> Void)?
// MARK: - Biometric Consent Callback Closures
var onGetIDVBiometricOptInConsent: ((_ userID: String, _ attemptsLeft: Int, _ challengeMode: RDNAChallengeOpMode, _ idvResponseJSON: String, _ status: [AnyHashable: Any], _ challengeResponse: RDNAChallengeResponse, _ error: RDNAError) -> Void)?
}
// MARK: - RDNAIDVKYCManagementCallbacks Protocol
extension RDNAIDVDelegateManager {
/// onIDVActivatedCustomerKYCResponse - Called with activated customer KYC response
/// **Callback Flow:**
/// 1. User calls initiateActivatedCustomerKYC("Post Login KYC")
/// 2. SDK triggers IDV workflow events (document scan, selfie, biometric consent)
/// 3. After workflow completes, this callback fires
/// 4. Navigate back to ActivatedCustomerKYCViewController
/// 5. Show success/error alert modal
///
/// **Dual Validation Pattern:**
/// - First check: error.longErrorCode (SDK synchronous error)
/// - Second check: status.statusCode (backend async validation)
/// - Success: longErrorCode == 0 AND (statusCode == 100 OR statusCode == 0)
func onIDVActivatedCustomerKYCResponse(_ userID: String, status: RDNARequestStatus, error: RDNAError) {
print("RDNAIDVDelegateManager - onIDVActivatedCustomerKYCResponse called")
print("RDNAIDVDelegateManager - UserID: \\(userID)")
print("RDNAIDVDelegateManager - Error Code: \\(error.longErrorCode) - \\(error.errorString ?? "")")
print("RDNAIDVDelegateManager - Status Code: \\(status.statusCode) - \\(status.statusMessage ?? "")")
DispatchQueue.main.async { [weak self] in
self?.onIDVActivatedCustomerKYCResponse?(userID, status, error)
}
}
// Other IDV protocol methods implemented here...
// (Document scan, selfie, biometric consent delegate methods)
}
[weak self]The post-login KYC flow is accessed through the side menu (drawer navigation) available from the Dashboard. The menu includes a dedicated "Activated Customer KYC" option that navigates users to the KYC entry point.
Add the KYC menu item to MenuViewController.swift:
// Sources/Tutorial/Screens/MFA/MenuViewController.swift
enum MenuAction {
case dashboard
case deviceManagement
case activatedCustomerKYC // π New menu action
// ... other actions
}
private func setupMenuItems() {
menuItems = [
MenuItem(icon: "π ", title: "Dashboard", subtitle: nil, action: .dashboard),
MenuItem(icon: "π±", title: "Device Management", subtitle: nil, action: .deviceManagement),
MenuItem(icon: "β", title: "Activated Customer KYC", subtitle: nil, action: .activatedCustomerKYC), // π New item
// ... other items
]
}
private func handleMenuAction(_ action: MenuAction) {
switch action {
case .activatedCustomerKYC:
navigateToActivatedCustomerKYC(using: navigationController)
// ... other cases
}
}
private func navigateToActivatedCustomerKYC(using navController: UINavigationController?) {
// Check if already on the screen
if navController?.viewControllers.last is ActivatedCustomerKYCViewController {
return
}
// Navigate using IDV Coordinator
AppIDVCoordinator.shared.showActivatedCustomerKYC()
}
Add the public navigation method to AppIDVCoordinator.swift:
// Sources/Tutorial/Navigation/AppIDVCoordinator.swift
class AppIDVCoordinator {
static let shared = AppIDVCoordinator()
/// Navigate to Activated Customer KYC screen (Post-Login)
func showActivatedCustomerKYC() {
guard let vc = storyboard.instantiateViewController(
withIdentifier: "ActivatedCustomerKYCViewController"
) as? ActivatedCustomerKYCViewController else {
print("IDVCoordinator - Failed to instantiate ActivatedCustomerKYCViewController")
return
}
DispatchQueue.main.async { [weak self] in
self?.navigationController?.pushViewController(vc, animated: true)
print("IDVCoordinator - Navigated to ActivatedCustomerKYC")
}
}
}

The ActivatedCustomerKYCViewController is the entry point for post-login KYC verification. It displays KYC information, educational content, and initiates the KYC workflow.
Initial State - Ready to Start KYC |
Processing State - KYC Initiated |
// Sources/Tutorial/Screens/IDV/ActivatedCustomerKYCViewController.swift
import UIKit
import RELID
/**
* ActivatedCustomerKYCViewController
*
* Entry point screen for post-login KYC verification flow for activated customers.
* Features:
* - Initiates activated customer KYC workflow via initiateActivatedCustomerKYC API
* - View controller-level persistent event handling for onIDVActivatedCustomerKYCResponse
* - Dual validation pattern (SDK sync error + backend status code)
* - Educational content about KYC process
* - Tutorial-style UI with numbered steps
*
* **Event Handler Lifecycle:**
* - Registered on viewDidLoad (persists even when IDV screens are on top)
* - Only cleanup on deinit
* - Navigates back to this screen automatically after KYC completion
*
* **Dual Validation Pattern:**
* 1. Check error.longErrorCode first (SDK synchronous error)
* 2. Then check status.statusCode (backend asynchronous validation)
* 3. Success only if: longErrorCode == 0 AND (statusCode == 100 OR statusCode == 0)
*/
class ActivatedCustomerKYCViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var menuButton: UIButton!
@IBOutlet weak var headerTitleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
// KYC Information
@IBOutlet weak var kycInfoContainer: UIView!
@IBOutlet weak var kycInfoTitleLabel: UILabel!
@IBOutlet weak var kycInfoTextLabel: UILabel!
// Guidelines
@IBOutlet weak var guidelinesContainer: UIView!
@IBOutlet weak var step1TitleLabel: UILabel!
@IBOutlet weak var step2TitleLabel: UILabel!
@IBOutlet weak var step3TitleLabel: UILabel!
@IBOutlet weak var step4TitleLabel: UILabel!
// Requirements
@IBOutlet weak var requirementsContainer: UIView!
@IBOutlet weak var requirement1Label: UILabel!
@IBOutlet weak var requirement2Label: UILabel!
@IBOutlet weak var requirement3Label: UILabel!
@IBOutlet weak var requirement4Label: UILabel!
// Actions
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
// Error Banner
@IBOutlet weak var errorBannerContainer: UIView!
@IBOutlet weak var errorBannerLabel: UILabel!
// MARK: - State Properties
private var isProcessing = false {
didSet {
updateStartButtonState()
}
}
private var errorMessage: String? {
didSet {
updateErrorBanner()
}
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupEventHandlers()
print("ActivatedCustomerKYCViewController - View loaded")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reset state on screen focus
isProcessing = false
errorMessage = nil
print("ActivatedCustomerKYCViewController - View will appear, state reset")
}
deinit {
// Cleanup event handler
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = nil
print("ActivatedCustomerKYCViewController - Event handler cleaned up")
}
// MARK: - Setup
private func setupUI() {
// Configure static UI elements
headerTitleLabel.text = "Activated Customer KYC"
subtitleLabel.text = "Complete your identity verification"
kycInfoTitleLabel.text = "What is KYC?"
kycInfoTextLabel.text = "KYC (Know Your Customer) is a verification process to confirm your identity. This helps ensure the security of your account and complies with regulatory requirements."
step1TitleLabel.text = "1. Document Scan"
step2TitleLabel.text = "2. Confirm Document Details"
step3TitleLabel.text = "3. Selfie Capture"
step4TitleLabel.text = "4. Biometric Consent"
requirement1Label.text = "β’ Have a valid government-issued ID ready"
requirement2Label.text = "β’ Ensure good lighting for document scanning"
requirement3Label.text = "β’ Position your face clearly for selfie capture"
requirement4Label.text = "β’ Allow camera permissions when prompted"
startButton.layer.cornerRadius = 8
loadingIndicator.hidesWhenStopped = true
errorBannerContainer.isHidden = true
}
// MARK: - Event Handlers Setup
private func setupEventHandlers() {
// Register view controller-level persistent event handler for onIDVActivatedCustomerKYCResponse
// This handler persists even when other IDV screens are on top
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = { [weak self] userID, status, error in
self?.handleActivatedCustomerKYCResponse(userID: userID, status: status, error: error)
}
print("ActivatedCustomerKYCViewController - Event handler registered (persistent)")
}
// MARK: - Event Handler Implementation
private func handleActivatedCustomerKYCResponse(userID: String, status: RDNARequestStatus, error: RDNAError) {
print("ActivatedCustomerKYCViewController - handleActivatedCustomerKYCResponse called")
print("ActivatedCustomerKYCViewController - UserID: \\(userID)")
print("ActivatedCustomerKYCViewController - Error: \\(error.longErrorCode) - \\(error.errorString ?? "")")
print("ActivatedCustomerKYCViewController - Status: \\(status.statusCode) - \\(status.statusMessage ?? "")")
// Navigate back to this screen (in case we're on another IDV screen)
if let navController = navigationController {
for vc in navController.viewControllers {
if vc is ActivatedCustomerKYCViewController {
navController.popToViewController(vc, animated: true)
break
}
}
}
// Dual validation pattern
let errorCode = error.longErrorCode
let statusCode = status.statusCode
var isSuccess = false
var message = ""
// Check sync error first
if errorCode != 0 {
// SDK sync error
isSuccess = false
message = "KYC verification failed: \\(error.errorString ?? "Unknown error")"
} else if statusCode != 100 && statusCode != 0 {
// Backend error
isSuccess = false
message = "KYC verification failed: \\(status.statusMessage ?? "Server error")"
} else {
// Success
isSuccess = true
message = "KYC verification completed successfully! Your identity has been verified."
}
// Reset state
isProcessing = false
// Show alert modal
let alert = UIAlertController(
title: isSuccess ? "Success" : "Error",
message: message,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Go to Dashboard", style: .default) { [weak self] _ in
self?.navigateToDashboard()
})
present(alert, animated: true)
}
// MARK: - IBActions
@IBAction func menuButtonTapped(_ sender: UIButton) {
// Open side menu
print("ActivatedCustomerKYCViewController - Menu button tapped")
MenuViewController.presentMenu(from: self)
}
@IBAction func startButtonTapped(_ sender: UIButton) {
print("ActivatedCustomerKYCViewController - Start button tapped")
guard !isProcessing else {
print("ActivatedCustomerKYCViewController - Button disabled (processing)")
return
}
isProcessing = true
errorMessage = nil
// Call SDK API
let error = RDNAIDVService.shared.initiateActivatedCustomerKYC(reason: "Post Login KYC")
if error.longErrorCode == 0 {
print("ActivatedCustomerKYCViewController - KYC initiated successfully")
print("ActivatedCustomerKYCViewController - SDK will auto-trigger IDV workflow events")
// SDK will automatically trigger IDV workflow events
// Keep isProcessing true until final callback
} else {
print("ActivatedCustomerKYCViewController - Failed to initiate KYC: \\(error.longErrorCode) - \\(error.errorString ?? "")")
isProcessing = false
errorMessage = error.errorString ?? "Failed to initiate KYC verification"
}
}
// MARK: - Navigation
private func navigateToDashboard() {
print("ActivatedCustomerKYCViewController - Navigating to Dashboard")
// Navigate to Dashboard
if let navController = navigationController {
for vc in navController.viewControllers {
if vc is DashboardViewController {
navController.popToViewController(vc, animated: true)
return
}
}
}
// If Dashboard not found in stack, pop to root
navigationController?.popToRootViewController(animated: true)
}
// MARK: - UI Updates
private func updateStartButtonState() {
startButton.isEnabled = !isProcessing
if isProcessing {
startButton.setTitle("Initiating...", for: .normal)
startButton.backgroundColor = UIColor(hex: "#3498db")?.withAlphaComponent(0.7)
loadingIndicator.startAnimating()
} else {
startButton.setTitle("Start KYC Verification", for: .normal)
startButton.backgroundColor = UIColor(hex: "#3498db")
loadingIndicator.stopAnimating()
}
}
private func updateErrorBanner() {
if let error = errorMessage, !error.isEmpty {
errorBannerLabel.text = error
errorBannerContainer.isHidden = false
} else {
errorBannerContainer.isHidden = true
}
}
}
Event Handler Registration:
private func setupEventHandlers() {
// Registered on viewDidLoad, persists until deinit
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = { [weak self] userID, status, error in
self?.handleActivatedCustomerKYCResponse(userID: userID, status: status, error: error)
}
}
KYC Initiation:
let error = RDNAIDVService.shared.initiateActivatedCustomerKYC(reason: "Post Login KYC")
if error.longErrorCode == 0 {
// Success: SDK will auto-trigger IDV workflow events
// Keep isProcessing true until final callback
} else {
// Sync error: Show error message
isProcessing = false
errorMessage = error.errorString
}
Dual Validation Pattern:
private func handleActivatedCustomerKYCResponse(userID: String, status: RDNARequestStatus, error: RDNAError) {
// Check SDK sync error
if error.longErrorCode != 0 {
// SDK error
showError(error.errorString)
} else if status.statusCode != 100 && status.statusCode != 0 {
// Backend error
showError(status.statusMessage)
} else {
// Success
showSuccess()
}
}
The document scan process for post-login KYC uses workflow 6 (IDV_POSTLOGIN_KYC) and challenge mode 9. The close button behavior is different from pre-login flow.
When SDK triggers getIDVDocumentScanProcessStartConfirmation delegate method via onGetIDVDocumentScanProcessStartConfirmation closure:
// Sources/Tutorial/Screens/IDV/IDVDocumentProcessStartConfirmationViewController.swift
import UIKit
import RELID
class IDVDocumentProcessStartConfirmationViewController: UIViewController {
// Workflow passed by AppIDVCoordinator
var idvWorkflow: RDNAIDVWorkflow = .IDV_ACTIVATION
private var isProcessing = false
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var closeButton: UIButton!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
// User confirms to start document scan
@IBAction func startDocumentScanTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
print("IDVDocumentProcessStart - Starting document scan with workflow: \(idvWorkflow.rawValue)")
// Pass workflow 6 to SDK
let error = RDNAIDVService.shared.setIDVDocumentScanProcessStartConfirmation(
isConfirm: true,
idvWorkflow: idvWorkflow // 6 for post-login KYC
)
if error.longErrorCode == 0 {
print("IDVDocumentProcessStart - Document scan initiated")
// SDK will open native camera for document capture
} else {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to start document scan")
}
}
// Close button behavior: resetAuthState for pre-login, cancel API for post-login
@IBAction func closeTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
// Check workflow to determine close behavior
if idvWorkflow == .IDV_ACTIVATION || idvWorkflow.rawValue == 5 {
// Pre-login flow: Call resetAuthState
let error = RDNAService.shared.resetAuthState()
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
} else {
// Post-login flow (workflow 6): Call cancel API
print("IDVDocumentProcessStart - Post-login flow, calling cancel API")
let error = RDNAIDVService.shared.setIDVDocumentScanProcessStartConfirmation(
isConfirm: false,
idvWorkflow: idvWorkflow // 6
)
// This triggers onIDVActivatedCustomerKYCResponse event with error 240 (User Cancelled)
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
}
}
}
After document capture, SDK triggers getIDVConfirmDocumentDetails delegate method with challenge mode 9:
// Sources/Tutorial/Screens/IDV/IDVConfirmDocumentDetailsViewController.swift
import UIKit
import RELID
class IDVConfirmDocumentDetailsViewController: UIViewController {
// Challenge mode passed by AppIDVCoordinator
var challengeMode: RDNAChallengeOpMode = RDNAChallengeOpMode(rawValue: 0)!
var responseDataJSON: String = ""
private var isProcessing = false
@IBOutlet weak var confirmButton: UIButton!
@IBOutlet weak var recaptureButton: UIButton!
@IBOutlet weak var closeButton: UIButton!
// User confirms document details are correct
@IBAction func confirmDocumentTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
print("IDVConfirmDocumentDetails - Confirming document with mode: \(challengeMode.rawValue)")
let error = RDNAIDVService.shared.setIDVConfirmDocumentDetails(
isConfirm: true,
challengeMode: challengeMode // 9 for post-login
)
if error.longErrorCode == 0 {
print("IDVConfirmDocumentDetails - Document confirmed, waiting for selfie event")
} else {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to confirm document")
}
}
// Close button: Challenge mode-based navigation
@IBAction func closeTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
if challengeMode.rawValue == 8 {
// Pre-login flow: Call resetAuthState
print("IDVConfirmDocumentDetails - Pre-login flow, calling resetAuthState")
let error = RDNAService.shared.resetAuthState()
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
} else {
// Post-login flow (mode 9): Call cancel API
print("IDVConfirmDocumentDetails - Post-login flow, calling cancel API")
let error = RDNAIDVService.shared.setIDVConfirmDocumentDetails(
isConfirm: false,
challengeMode: challengeMode // 9
)
// This triggers onIDVActivatedCustomerKYCResponse event
// Persistent handler navigates back to ActivatedCustomerKYC screen
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
}
}
}
Test the document scan process:
Test Close Button:
onIDVActivatedCustomerKYCResponse event with error 240The selfie capture process for post-login KYC also uses workflow 6 and challenge mode 9, with different close button navigation patterns.
When SDK triggers getIDVSelfieProcessStartConfirmation delegate method:
// Sources/Tutorial/Screens/IDV/IDVSelfieProcessStartConfirmationViewController.swift
import UIKit
import RELID
class IDVSelfieProcessStartConfirmationViewController: UIViewController {
var idvWorkflow: RDNAIDVWorkflow = .IDV_ACTIVATION
private var isProcessing = false
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var cameraSwitch: UISwitch!
// User confirms to start selfie capture
@IBAction func startSelfieTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
print("IDVSelfieProcessStart - Starting selfie capture with workflow: \(idvWorkflow.rawValue)")
let error = RDNAIDVService.shared.setIDVSelfieProcessStartConfirmation(
isConfirm: true,
useDeviceBackCamera: cameraSwitch.isOn, // false = front camera for selfies
idvWorkflow: idvWorkflow // 6 for post-login KYC
)
if error.longErrorCode == 0 {
print("IDVSelfieProcessStart - Selfie capture initiated")
// SDK will open native camera for selfie with liveness detection
} else {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to start selfie capture")
}
}
// Close button behavior based on workflow
@IBAction func closeTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
if idvWorkflow == .IDV_ACTIVATION || idvWorkflow.rawValue == 5 {
// Pre-login flow: Call resetAuthState
let error = RDNAService.shared.resetAuthState()
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
} else {
// Post-login flow (workflow 6): Call cancel API
print("IDVSelfieProcessStart - Post-login flow, calling cancel API")
let error = RDNAIDVService.shared.setIDVSelfieProcessStartConfirmation(
isConfirm: false,
useDeviceBackCamera: false,
idvWorkflow: idvWorkflow // 6
)
// This triggers onIDVActivatedCustomerKYCResponse event with error 240
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
}
}
}
After selfie capture, SDK triggers getIDVSelfieConfirmation delegate method with challenge mode 9:
// Sources/Tutorial/Screens/IDV/IDVSelfieConfirmationViewController.swift
import UIKit
import RELID
class IDVSelfieConfirmationViewController: UIViewController {
var userID: String = ""
var challengeMode: RDNAChallengeOpMode = RDNAChallengeOpMode(rawValue: 0)!
var responseDataJSON: String = ""
private var isProcessing = false
@IBOutlet weak var confirmButton: UIButton!
// User confirms selfie biometric results
@IBAction func confirmSelfieTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
print("IDVSelfieConfirmation - Confirming selfie with mode: \(challengeMode.rawValue)")
let error = RDNAIDVService.shared.setIDVSelfieConfirmation(
action: "true", // String value, not Bool!
challengeMode: challengeMode // 9 for post-login
)
if error.longErrorCode == 0 {
print("IDVSelfieConfirmation - Selfie confirmed, waiting for consent event")
} else {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to confirm selfie")
}
}
// Close button: Challenge mode-based navigation
// Important: No cancel API available for selfie confirmation!
@IBAction func closeTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
if challengeMode.rawValue == 8 {
// Pre-login flow: Call resetAuthState
print("IDVSelfieConfirmation - Pre-login flow, calling resetAuthState")
let error = RDNAService.shared.resetAuthState()
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
} else {
// Post-login flow (mode 9): Navigate directly to ActivatedCustomerKYC
// No cancel API available for selfie confirmation
print("IDVSelfieConfirmation - Post-login flow, navigating directly to ActivatedCustomerKYC")
isProcessing = false
if let navController = navigationController {
for vc in navController.viewControllers {
if vc is ActivatedCustomerKYCViewController {
navController.popToViewController(vc, animated: true)
print("IDVSelfieConfirmation - Navigated to ActivatedCustomerKYC, button will be auto-enabled")
return
}
}
}
// If not found, pop to root
navigationController?.popToRootViewController(animated: true)
}
}
}
Test the selfie capture process:
Test Close Button:
The biometric consent for post-login KYC uses challenge mode 9 with a different response structure compared to pre-login flow.
Aspect | Pre-Login (Mode 8) | Post-Login (Mode 9) |
challengeResponse | Present | Optional/Absent |
status location |
| Root level |
Swift access | Via challengeResponse parameter | Via status parameter |
When SDK triggers getIDVBiometricOptInConsent delegate method with challenge mode 9:
// Sources/Tutorial/Screens/IDV/IDVBiometricOptInConsentViewController.swift
import UIKit
import RELID
class IDVBiometricOptInConsentViewController: UIViewController {
var userID: String = ""
var attemptsLeft: Int = 0
var challengeMode: RDNAChallengeOpMode = RDNAChallengeOpMode(rawValue: 0)!
var idvResponseJSON: String = ""
var status: [AnyHashable: Any] = [:]
var challengeResponse: RDNAChallengeResponse?
var sdkError: RDNAError?
private var isProcessing = false
@IBOutlet weak var approveButton: UIButton!
@IBOutlet weak var rejectButton: UIButton!
// User accepts or rejects biometric template storage
@IBAction func approveTapped(_ sender: UIButton) {
handleConsent(isOptIn: true)
}
@IBAction func rejectTapped(_ sender: UIButton) {
handleConsent(isOptIn: false)
}
private func handleConsent(isOptIn: Bool) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
print("IDVBiometricOptInConsent - Processing consent with mode: \(challengeMode.rawValue), isOptIn: \(isOptIn)")
let error = RDNAIDVService.shared.setIDVBiometricOptInConsent(
isOptIn: isOptIn, // true = accept, false = reject
challengeMode: challengeMode // 9 for post-login
)
if error.longErrorCode == 0 {
print("IDVBiometricOptInConsent - Consent processed successfully")
// SDK will trigger onIDVActivatedCustomerKYCResponse event with completion status
} else {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to process consent")
}
}
// Close button: Challenge mode-based navigation
@IBAction func closeTapped(_ sender: UIButton) {
guard !isProcessing else { return }
isProcessing = true
updateButtonStates()
if challengeMode.rawValue == 8 {
// Pre-login flow: Call resetAuthState
print("IDVBiometricOptInConsent - Pre-login flow, calling resetAuthState")
let error = RDNAService.shared.resetAuthState()
if error.longErrorCode != 0 {
isProcessing = false
updateButtonStates()
showError(error.errorString ?? "Failed to cancel")
}
} else {
// Post-login flow (mode 9): Navigate directly to ActivatedCustomerKYC
print("IDVBiometricOptInConsent - Post-login flow, navigating directly to ActivatedCustomerKYC")
isProcessing = false
if let navController = navigationController {
for vc in navController.viewControllers {
if vc is ActivatedCustomerKYCViewController {
navController.popToViewController(vc, animated: true)
print("IDVBiometricOptInConsent - Navigated to ActivatedCustomerKYC, button will be auto-enabled")
return
}
}
}
// If not found, pop to root
navigationController?.popToRootViewController(animated: true)
}
}
}
In the IDV delegate manager, parameters handle both structures:
// Closure property for biometric consent
var onGetIDVBiometricOptInConsent: (
(_ userID: String,
_ attemptsLeft: Int,
_ challengeMode: RDNAChallengeOpMode,
_ idvResponseJSON: String,
_ status: [AnyHashable: Any],
_ challengeResponse: RDNAChallengeResponse,
_ error: RDNAError) -> Void
)?
// Protocol implementation
func getIDVBiometricOpt(
inConsent userID: String,
response: RDNAChallengeResponse,
challenge mode: RDNAChallengeOpMode,
error: RDNAError
) {
print("RDNAIDVDelegateManager - Biometric opt-in consent event received")
print("RDNAIDVDelegateManager - User ID: \(userID)")
print("RDNAIDVDelegateManager - Challenge Mode: \(mode.rawValue)")
// For mode 9, status is at root level
// For mode 8, status is in challengeResponse
// SDK provides both parameters, use appropriate one based on mode
DispatchQueue.main.async { [weak self] in
self?.onGetIDVBiometricOptInConsent?(
userID,
0, // attemptsLeft
mode,
"", // idvResponseJSON
[:], // status dictionary
response,
error
)
}
}
Test the biometric consent process:
setIDVBiometricOptInConsent(true, 9) calledonIDVActivatedCustomerKYCResponse eventTest Close Button:
Test Reject Consent:
setIDVBiometricOptInConsent(false, 9) calledNow that you've implemented all the components, let's understand how they work together in the complete KYC flow.
1. User Action (ActivatedCustomerKYCViewController)
ββ User clicks "Start KYC Verification"
ββ View controller calls: RDNAIDVService.shared.initiateActivatedCustomerKYC("Post Login KYC")
ββ View controller registers persistent handler in viewDidLoad()
2. Service Layer (RDNAIDVService.swift)
ββ Calls SDK: rdna.initiateActivatedCustomerKYC(reason)
ββ SDK returns RDNAError (synchronous)
ββ Sync success: error.longErrorCode = 0
ββ SDK initiates IDV workflow (workflow 6, mode 9)
3. IDV Workflow Events (SDK β Delegate Manager β Coordinator β ViewControllers)
ββ getIDVDocumentScanProcessStartConfirmation β Document Scan
ββ getIDVConfirmDocumentDetails β Document Validation
ββ getIDVSelfieProcessStartConfirmation β Selfie Capture
ββ getIDVSelfieConfirmation β Selfie Verification
ββ getIDVBiometricOptInConsent β Biometric Consent
4. KYC Completion (onIDVActivatedCustomerKYCResponse)
ββ SDK triggers delegate method (after all steps complete or cancellation)
ββ RDNAIDVDelegateManager receives call
ββ Delegate Manager: onIDVActivatedCustomerKYCResponse closure invoked
ββ DispatchQueue.main.async ensures main thread
ββ View Controller Handler: Closure registered in viewDidLoad()
5. View Controller Handler Processing
ββ Navigate back to ActivatedCustomerKYCViewController
β β’ Iterate through navigation stack
β β’ popToViewController(vc, animated: true)
ββ Dual Validation:
β ββ Check error.longErrorCode (0 = OK, 240 = cancelled, others = error)
β ββ Check status.statusCode (100/0 = OK, others = backend error)
ββ Show Success/Error UIAlertController
ββ Provide Dashboard navigation option
The view controller-level handler persists throughout the IDV flow:
// In ActivatedCustomerKYCViewController
override func viewDidLoad() {
super.viewDidLoad()
setupEventHandlers()
}
private func setupEventHandlers() {
// This handler persists even when other screens push on top
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = { [weak self] userID, status, error in
self?.handleActivatedCustomerKYCResponse(userID: userID, status: status, error: error)
}
}
deinit {
// Only cleanup when view controller is deallocated
RDNAIDVDelegateManager.shared.onIDVActivatedCustomerKYCResponse = nil
}
Lifecycle Point | Handler State | Purpose |
viewDidLoad | Registered | Set up persistent closure |
viewWillDisappear | Still active | Persists when other screens push |
Other IDV screens on top | Still active | Catches completion event |
deinit | Cleaned up | Only when VC deallocates |
Test the complete KYC completion flow:
setIDVBiometricOptInConsent(true, 9) calledonIDVActivatedCustomerKYCResponse protocol method calledpopToViewController(vc, animated: true) calledTest Cancellation:
onIDVActivatedCustomerKYCResponse with error 240Test Direct Navigation:
relidcodelab.xcworkspace (not .xcodeproj!)# Clean build
xcodebuild -workspace relidcodelab.xcworkspace \\
-scheme relidcodelab \\
-sdk iphonesimulator clean build
# Run on simulator
xcodebuild -workspace relidcodelab.xcworkspace \\
-scheme relidcodelab \\
-sdk iphonesimulator \\
-destination 'platform=iOS Simulator,name=iPhone 14'
print() statements and SDK logsFollow these steps to test the complete post-login KYC flow:
onIDVActivatedCustomerKYCResponse eventSuccess Scenario:
β
KYC initiated successfully
β
Document scanned and validated
β
Selfie captured and matched
β
Biometric consent approved
β
Alert: "KYC verification completed successfully!"
β
Navigate to Dashboard
Error Scenarios:
β Sync Error: error.longErrorCode != 0
β Show error message immediately
β Backend Error: status.statusCode != 100 && statusCode != 0
β Show error message in completion alert
β User Cancellation: error.longErrorCode == 240
β Show "KYC cancelled" message in completion alert
Problem: Framework not linked properly
Solution:
# Clean build folder
rm -rf ~/Library/Developer/Xcode/DerivedData
# Reinstall pods
pod deintegrate
pod install
# Clean and rebuild in Xcode
Cmd+Shift+K (Clean)
Cmd+B (Build)
Problem: Missing Regula database or license
Solution:
db.dat exists in RELID_IDV frameworkregula.license exists and is validCertificates.bundle is includedProblem: Missing Info.plist permissions
Solution:
<!-- Add to Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Camera access is required for document scanning and selfie capture</string>
Problem: Event handler not registered or deallocated
Solution:
viewDidLoad(), not viewWillAppear()[weak self] in closure to prevent retain cyclesnil until deinitProblem: View controller not found in navigation stack
Solution:
// Check if view controller exists before navigating
if let navController = navigationController {
for vc in navController.viewControllers {
if vc is ActivatedCustomerKYCViewController {
navController.popToViewController(vc, animated: true)
return
}
}
}
// Fallback: pop to root
navigationController?.popToRootViewController(animated: true)
Problem: IBOutlet not connected
Solution:
Problem: Version conflicts or pod repo out of date
Solution:
# Update pod repo
pod repo update
# Install with repo update
pod install --repo-update
# If still failing, try clean install
rm -rf Pods
rm Podfile.lock
pod install
Problem: Framework not embedded
Solution:
Congratulations! You've successfully implemented the RELID IDV Post-Login KYC Flow for iOS. You now have:
Architecture Patterns:
Workflow Specifics:
Validation Patterns:
Now that you've completed the post-login KYC flow, consider exploring:
We'd love to hear your feedback! Please submit issues or suggestions on our GitHub repository.
Thank you for completing the RELID IDV Post-Login KYC Flow iOS Codelab!