# iOS SDK
# Getting started
Latest release: Version 1.28.0 (Changelog)
# Requirements
- Xcode 14.1+
- iOS 11.0 or later
- MRTDReader module requires iOS 13+
Ensure bitcode is disabled for your project
# Resources
- IdensicMobileSDK iOS Demo - a demo project in Swift.
- sample.swift - Swift sample code. Feel free to use it as a boilerplate.
- applicantActions-sample.swift - Sample code for the usage in Applicant Actions mode.
# Installation
This framework is available to install with Swift Package Manager or CocoaPods.
# Swift Package Manager
Add the https://github.com/SumSubstance/IdensicMobileSDK-iOS.git
to your project as a package dependency.
- Add
IdensicMobileSDK
library to your target. - Add optional libraries if you need them, see for details MRTDReader and VideoIdent for details.
Note: Twilio since 5.0.0
only supports iOS 12.2+. If you want to use Twilio higher than 4.6.3
, please set the ios-12.2
branch when adding the package dependency and ensure that your minimum deployment target is iOS 12.2+.
# CocoaPods
Update your Podfile
:
- Add
source
options for SumSubstance and CocoaPods repositories. - Add
IdensicMobileSDK
dependency to your target. - Add optional dependencies if you need them, see for details MRTDReader and VideoIdent for details.
platform :ios, '11.0'
source 'https://cdn.cocoapods.org/'
source 'https://github.com/SumSubstance/Specs.git'
target 'YourApp' do
pod 'IdensicMobileSDK'
# pod 'IdensicMobileSDK/MRTDReader'
# pod 'IdensicMobileSDK/VideoIdent'
# any other dependencies
end
Then run this in your project directory
pod install
# Permissions
The framework will ask for access to the camera and possibly the microphone, the photo library and geolocation too.
Because of this, it is required to have the corresponding usage descriptions in the application's Info.plist
file:
<key>NSCameraUsageDescription</key>
<string>Let us take a photo</string>
<key>NSMicrophoneUsageDescription</key>
<string>Time to record a video</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Let us pick a photo</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Please provide us with your geolocation data to prove your current location</string>
Also, if your app targets iOS 10 and below, it's required to turn on the iCloud Documents capabilities in Xcode as described here
# Modules
# MRTDReader (NFC)
The MRTDReader
is an optional module that allows the sdk to detect and read the electronic chips placed on MRTD documents via NFC. In order to enable this functionality please follow the instructions below.
- Add the module:
- Swift Package Manager: Add
IdensicMobileSDK_MRTDReader
library to your target. - CocoaPods: Add
IdensicMobileSDK/MRTDReader
dependency into yourPodfile
.
Turn on
Near Field Communication Tag Reading
capability for your project’s target (here is how to)Update the application's
Info.plist
file as follows:
<key>NFCReaderUsageDescription</key>
<string>Let us scan the document for more precise recognition</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
# VideoIdent
The VideoIdent
is an optional module that is required only if you are going to use the Video Identification during the verification flow.
In order to enable the module please follow the instructions below.
- Add the module:
- Swift Package Manager: Add
IdensicMobileSDK_VideoIdent
library to your target. - CocoaPods: Add
IdensicMobileSDK/VideoIdent
dependency into yourPodfile
.
- Add
Background mode
capability for your project’s target (here is how to) and selectAudio, AirPlay, and Picture in Picture
option there. That's required for the video call not to be broken when the application goes into background.
# Basic Usage
Make sure you did the Backend routines before initializing the SDK
# Initialization
First of all, import the framework:
import IdensicMobileSDK
Then declare the initialization parameters:
let accessToken = "..." // get the `accessToken` from your backend
As you can see the only thing you need is an accessToken
from your backend. The token points to an applicant level, that essintially configures the steps of the verification process, and indicates a customer to be verified. The sdk will work in the production or in the sandbox environment depend on which one the accessToken
has been generated on (see Backend routines for details).
Next you instantiate SNSMobileSDK
and check if setup succeeded:
let sdk = SNSMobileSDK(
accessToken: accessToken
)
guard sdk.isReady else {
print("Initialization failed: " + sdk.verboseStatus)
return
}
Here you create an instance of the SDK and ensure that the setup was successful with sdk.isReady
, if it happens to fail, you will be able to pinpoint the reason by printing sdk.verboseStatus
.
The next important step is to set the tokenExpirationHandler
. The matter is that typically the access token is valid for a rather short period of time and when it's expired you must provide another one (see Token Expiration for details).
sdk.tokenExpirationHandler { (onComplete) in
get_token_from_your_backend { (newToken) in
onComplete(newToken)
}
}
Most likely, you will then tune up the sdk a bit further by assigning Handlers and Callbacks, and possibly by applying some Configuration and Customization, but that's just an optional feature.
We know that the requirement to provide the accessToken
as an initialization parameter forces you to ask your backend and due to the async nature of this process you'll have to build some UI around. For your convenience, it's possible to postpone the provision of the token until the sdk has been presented. To do so, you will need to pass an empty string as the accessToken
at the initialization stage. This way the tokenExpirationHandler
will be called immediately after the sdk is appeared up at the time when an activity indicator is displayed.
# Presentation
In any case once setup is done you are ready to present the SDK on the screen:
yourViewController.present(sdk.mainVC, animated: true, completion: nil)
Since SDK contains its own navigation stack, it's required to be presented modally instead of being pushed.
You can also use a shortcut like this:
sdk.present(from: yourViewController)
Or even shorter if it's comfortable for you to present the sdk on the key window's root view controller:
sdk.present()
# Dismission
By default, once the applicant is approved the sdk will be dismissed in 3 seconds automatically. You can adjust this time interval or switch the automatic dismissal off by setting value of zero:
sdk.setOnApproveDismissalTimeInterval(0)
Also, in case you need to close the sdk programmatically, here is the helper:
sdk.dismiss()
# Configuration
# Applicant Data
For your convenience and if it's required, you could provide an email and/or a phone number those will be assigned to the applicant initially.
sdk.initialEmail = "..." // applicant's email
sdk.initialPhone = "..." // applicant's phone number
# Preferred Documents
For IDENTITY*
steps it's possible to specify the preferred country and document type to be selected automatically bypassing the DocType Selector screen. Pay attention please that the parameters provided will be applied only if the corresponding combination of country
and idDocType
is allowed at the step according to the level configuration.
For example:
sdk.preferredDocumentDefinitions = [
.identity: SNSDocumentDefinition(
idDocType: "DRIVERS",
country: "USA"
)
]
# Handlers
Technically the handlers are optional, but you are strongly encouraged to provide at least tokenExpirationHandler
to make sure that the access token always is live.
# Token Expiration
Because of the limited lifespan of the accessToken
, it's important that you are able to handle the situation where the token expires and needs to be refreshed. As a solution to this, you set tokenExpirationHandler
. The handler should make a call to your backend, obtain the newToken
and then pass it back to the sdk by running onComplete
closure.
sdk.tokenExpirationHandler { (onComplete) in
get_token_from_your_backend { (newToken) in
onComplete(newToken)
}
}
⚠️ onComplete
must be executed even if you fail to provide a new token, just pass nil
in this case.
# Verification Completion
You can use verificationHandler
to be informed when the verification process has been concluded with a final decision. The parameter isApproved
lets you know if the applicant was approved or finally rejected. If you'd like to get notified about any other stages of the verification process, use onStatusDidChange
described below.
sdk.verificationHandler { (isApproved) in
print("verificationHandler: Applicant is " + (isApproved ? "approved" : "finally rejected"))
}
# Dismission Control
You can take over the dismissal control by assigning dismissHandler
. The handler takes the current sdk
instance and the mainVC
controller. It's up to you to dismiss the mainVC
in the manner that you see fit.
sdk.dismissHandler { (sdk, mainVC) in
mainVC.dismiss(animated: true, completion: nil)
}
# Callbacks
Callbacks are completely optional. Use them if you feel that they will be helpful.
# Status Updates Notification
Use onStatusDidChange
callback to get notified about the stages of the verification process.
The callback takes two parameters. The first one sdk
is the SDK instance, and the last one prevStatus
gives you a chance to know the previous value of the status
. Following this, you are able to examine sdk.status
enum in order to determine the current sdk status.
sdk.onStatusDidChange { (sdk, prevStatus) in
print("onStatusDidChange: [\(sdk.description(for: prevStatus))] -> [\(sdk.description(for: sdk.status))]")
switch sdk.status {
case .ready:
// Technically .ready couldn't ever be passed here, since the callback has been set after `status` became .ready
break
case .failed:
print("failReason: [\(sdk.description(for: sdk.failReason))] - \(sdk.verboseStatus)")
case .initial:
print("No verification steps are passed yet")
case .incomplete:
print("Some but not all of the verification steps have been passed over")
case .pending:
print("Verification is pending")
case .temporarilyDeclined:
print("Applicant has been temporarily declined")
case .finallyRejected:
print("Applicant has been finally rejected")
case .approved:
print("Applicant has been approved")
case .actionCompleted:
print("Applicant action has been completed")
}
}
# Events Notification
Subscribing to onEvent
callback allows you to be aware of the events happening along the processing.
Events are passed into the callback as instances of a class inherited from the base SNSEvent
class, this way each event has its eventType
and some parameters packed into payload
dictionary. So, depending on your needs, you can get event parameters either by examining the payload
directly or by casting the given event instance to a specific SNSEvent*
class according to its type.
sdk.onEvent { (sdk, event) in
switch event.eventType {
case .applicantLoaded:
if let event = event as? SNSEventApplicantLoaded {
print("onEvent: Applicant [\(event.applicantId)] has been loaded")
}
case .stepInitiated:
if let event = event as? SNSEventStepInitiated {
print("onEvent: Step \(event.idDocSetType) has been initiated")
}
case .stepCompleted:
if let event = event as? SNSEventStepCompleted {
print("onEvent: Step \(event.idDocSetType) has been \(event.isCancelled ? "cancelled" : "fulfilled")")
}
case .analytics:
if let event = event as? SNSEventAnalytics {
print("onEvent: Analytics event [\(event.eventName)] has occured with payload=\(event.eventPayload ?? [:])")
}
@unknown default:
print("onEvent: eventType=\(event.description(for: event.eventType)) payload=\(event.payload)")
}
}
# Dismiss Notification
An optional way to be notified when mainVC
is dismissed:
sdk.onDidDismiss { (sdk) in
print("onDidDismiss: sdk has been dismissed with status [\(sdk.description(for: sdk.status))]")
}
# Logging
There's no need to mention the importance of logs in the case that something goes wrong.
# Log Level
By default, SDK tries not to spam your console and will print only when something critical has happened, however, sometimes, it makes sense to know what's going on under the hood.
You can choose the desired logLevel
from .off
that logs nothing, through .error
(default), .warning
, .info
, .debug
and up to .trace
that will try to log as much as possible.
sdk.logLevel = .error
# Log Interception
By default, SDK uses NSLog
for logging purposes. If, for some reason, it does not work for you, feel free to use logHandler
to intercept log messages and direct them as required.
sdk.logHandler { (level, message) in
print(Date(), "[Idensic] \(message)")
}
# Analytics
The SDK collects and sends usage data to Sumsub servers. We don't track any sensitive data. Only general usage statistics is sent to us. It includes screen navigation events, UI controls interaction events and so on. We interpret the usage data in order to improve Sumsub. None of the data is sold or transfered to third parties, and we don't use it for advertisement.
If you still want to disable this feature, you can use the following to do this:
sdk.isAnalyticsEnabled = false
# Customization
# Theme
The theme
allows you to customize things such as fonts, colors and images used across SDK.
The default theme is accessible once the sdk is initialized. So depend on your needs you either adjust the theme in place:
sdk.theme.colors.backgroundCommon = .white
or inherit from SNSTheme
and apply your own theme at once:
sdk.theme = OwnTheme()
class OwnTheme: SNSTheme {
override init() {
super.init()
colors.backgroundCommon = .white
}
}
See Theme API and Visual Guide for details.
# Localization
You can customize or localize the texts used within the SDK through the MSDK Translations tool in the dashboard.
The language of the texts will be set according to the system locale, but you could override it by setting sdk.locale
to the locale you desire. Use the values in a form of en
or en_US
.
sdk.locale = Locale.current.identifier
Please check the list of supported locales by SDK here.
# Strings
In addition to the MSDK Translations tool and sdk.locale
described above, you can use sdk.strings
dictionary that allows you to define the strings locally. That could be helpful for example if you would like to avoid the Wordless Oops screen that could be rendered in case the network happens to be down during the sdk initialization and force the sdk to draw the Network Oops instead:
sdk.strings = [
"sns_oops_network_title": "Oops! Seems like the network is down.",
"sns_oops_network_html": "Please check your internet connection and try again.",
"sns_oops_action_retry": "Try again",
]
⚠️ Pay attention please that sdk.locale
does not affect these strings, thus it's up to you to use the required localization.
# Support Items
Support items define the ways in which your users will be prompted to contact you at the Support screen.
Initially an Email item would be created automatically using the Support email
configured in your dashboard.
Feel free to reconfigure support items as necessary. In order to do so, you could either assign an array of items to sdk.supportItems
property directly, or use sdk.addSupportItem
helper to add them one by one.
sdk.addSupportItem { (item) in
item.title = NSLocalizedString("URL Item", comment: "")
item.subtitle = NSLocalizedString("Tap me to open an url", comment: "")
item.icon = UIImage(named: "AppIcon")
item.actionURL = URL(string: "https://google.com")
}
sdk.addSupportItem { (item) in
item.title = NSLocalizedString("Callback Item", comment: "")
item.subtitle = NSLocalizedString("Tap me to get callback fired", comment: "")
item.icon = UIImage(named: "AppIcon")
item.actionHandler { (supportVC, item) in
print("[\(item.title)] tapped")
}
}
Each item must have a mandatory title
, optional icon
, subtitle
and an action expressed with actionURL
or actionHandler
. If actionHandler
is defined, it would be called when the item is tapped, otherwise actionURL
would be opened with UIApplication's openURL:
method. If no actionURL
and no actionHandler
are provided, then no action would be taken on tap.
# Applicant Actions
There is a special way to use the SDK in order to perform Applicant actions.
Only the Face authentication action is supported at the moment
# Action Level
In order to run the SDK in applicant action mode, you need to associate your applicant level with a customization of Applicant actions
type in the dashboard. Also, it'll be required to make an Access Token not only with the userId
and levelName
parameters, but with the externalActionId
one as well.
Aside from the notes above, you manage the sdk the same way that you do with regular levels, the only difference is in how you get the action's result.
When an action is completed, the sdk.status
will be set to .actionCompleted
and sdk.actionResult
will contain the outcome of the last action's invocation.
You can use onDidDismiss
, dismissHandler
or onStatusDidChange
callback in order to determine the sdk's status and get the action's result.
For example:
sdk.onDidDismiss { (sdk) in
switch sdk.status {
case .failed:
print("failReason: [\(sdk.description(for: sdk.failReason))] - \(sdk.verboseStatus)")
case .actionCompleted:
// the action was performed or cancelled
if let result = sdk.actionResult {
print("Last action result: actionId=\(result.actionId) answer=\(result.answer ?? "<none>")")
} else {
print("The action was cancelled")
}
default:
// in case of an action level, the other statuses are not used for now,
// but you could see them if the user closes the sdk before the level is loaded
break
}
}
# Action Result
The action's result is represented by the sdk.actionResult
property, that contains the following fields:
Field | Type | Description |
---|---|---|
actionId | String | Applicant action identifier to check the results against the server |
answer | String | Overall result. Typical values are GREEN , RED or ERROR |
The absence of the sdk.actionResult
means that the user has cancelled the process.
# Result Handler
In addition, there is an optional actionResultHandler
, that allows you to handle the action's result upon it's arrival from the backend.
The user sees the "Processing..." screen at this moment.
sdk.actionResultHandler { (sdk, result, onComplete) in
print("actionResultHandler: actionId=\(result.actionId) answer=\(result.answer ?? "<none>")")
// you are allowed to process the result asynchronously, just don't forget to call `onComplete` when you finish,
// you could pass `.cancel` to force the user interface to close, or `.continue` to proceed as usual
onComplete(.continue)
}
⚠️ onComplete
must be executed at the end of processing.