# iOS (Compat) Liveness SDK

[Get your Public Merchant Key](/getting-started/getting-your-public-merchant-key.md)

**Youverify Liveness SDK Compat** is the compatibility version of the iOS SDK, designed for integrating active liveness detection into UIKit-based applications. This SDK is tailored for apps that need to support iOS 12 and later, providing a reliable solution for older iOS versions.

### Features

* **Active Liveness Detection**: Requires users to perform specific actions (e.g., head movements, answering questions) to confirm liveness. Available on iOS 12+.
* Built with **UIKit**, ensuring compatibility with legacy iOS applications.

**Note**: Passive liveness detection is not supported in this version.

### Installation

#### Step 1: Add to Podfile

Add the following to your `Podfile`:

```ruby
pod 'YouverifyLivenessSDKCompat'
pod 'MediaPipeTaskVision'
```

#### Step 2: Install Dependencies

Run this command in your terminal:

```sh
pod install
```

#### Step 3: Open Workspace

Open the generated `.xcworkspace` file in Xcode.

#### Project Configuration

To prevent linker errors, add the following post-install script to your application's Podfile, making sure to replace `"YourAppTarget"` with the correct target name for your app.

post\_install do |installer|\
target\_name = "YourAppTarget"

```
# Get the paths to the xcconfig files
debug_xcconfig_path = "#{installer.pods_project.project_dir}/Target Support Files/Pods-#{target_name}/Pods-#{target_name}.debug.xcconfig"
release_xcconfig_path = "#{installer.pods_project.project_dir}/Target Support Files/Pods-#{target_name}/Pods-#{target_name}.release.xcconfig"

# Modify the debug .xcconfig file if it exists
if File.exist?(debug_xcconfig_path)
  puts "Modifying debug .xcconfig file: #{debug_xcconfig_path}"

  # Read the debug .xcconfig file
  debug_xcconfig = File.read(debug_xcconfig_path)

  # Remove framework flags from OTHER_LDFLAGS
  debug_xcconfig.gsub!(/-framework\s+"MediaPipeTasksCommon"/, '')
  debug_xcconfig.gsub!(/-framework\s+"MediaPipeTasksVision"/, '')

  # Remove -force_load from OTHER_LDFLAGS for iphoneos and iphonesimulator
  debug_xcconfig.gsub!(/-force_load\s+"?[^"]*libMediaPipeTasksCommon.*\.a"?/, '')

  # Write the modified debug .xcconfig file
  File.write(debug_xcconfig_path, debug_xcconfig)
  puts "Modified debug .xcconfig file."
else
  puts "Debug .xcconfig file not found at #{debug_xcconfig_path}"
end

# Modify the release .xcconfig file if it exists
if File.exist?(release_xcconfig_path)
  puts "Modifying release .xcconfig file: #{release_xcconfig_path}"

  # Read the release .xcconfig file
  release_xcconfig = File.read(release_xcconfig_path)

  # Remove framework flags from OTHER_LDFLAGS
  release_xcconfig.gsub!(/-framework\s+"MediaPipeTasksCommon"/, '')
  release_xcconfig.gsub!(/-framework\s+"MediaPipeTasksVision"/, '')

  # Remove -force_load from OTHER_LDFLAGS for iphoneos and iphonesimulator
  release_xcconfig.gsub!(/-force_load\s+"?[^"]*libMediaPipeTasksCommon.*\.a"?/, '')

  # Write the modified release .xcconfig file
  File.write(release_xcconfig_path, release_xcconfig)
  puts "Modified release .xcconfig file."
else
  puts "Release .xcconfig file not found at #{release_xcconfig_path}"
end
```

end

***

### Usage

#### 1. Import the SDK

In your Swift file:

```swift
import YouverifyLivenessSDK
```

#### 2. Initialize the SDK

Create an instance of `YVLiveness` with your configuration:

```swift
private var yvLiveness = YVLiveness(
    publicKey: Environment.shared.value(forKey: "API_KEY") ?? "",
    user: YVLivenessUser(firstName: "Ugochukwu"),
    onSuccess: { data in
        print("The success data is \(data)")
    },
    onFailure: { errorData in
        print("The error data is \(errorData)")
    },
    sandboxEnvironment: false
)
```

* The `publicKey` is dynamically retrieved from an environment variable (`Environment.shared.value(forKey: "API_KEY") ?? ""`) in the working code. Replace this with your actual API key or retrieval logic.
* The `user` parameter requires a `firstName`, while optional fields like `lastName` and `email` can be added if needed (see Configuration Options).

#### 3. Start Liveness Detection

Launch the SDK with a list of tasks:

```swift
yvLiveness.startSDK(tasks: tasks)
DispatchQueue.main.async {
    self.addLivenessViewController()
}
```

#### 4. Manage the Liveness View Controller

Add these methods to handle the liveness UI:

```swift
private var livenessViewController: YVLivenessViewController?

private func addLivenessViewController() {
    guard livenessViewController == nil else { return }
    let vc = YVLivenessViewController(sdk: yvLiveness)
    vc.delegate = self
    addChild(vc)
    view.addSubview(vc.view)
    vc.didMove(toParent: self)
    livenessViewController = vc
}

private func removeLivenessViewController() {
    guard let vc = livenessViewController else { return }
    vc.willMove(toParent: nil)
    vc.view.removeFromSuperview()
    vc.removeFromParent()
    livenessViewController = nil
}
```

* The `guard` statement in `addLivenessViewController()` prevents duplicate additions of the view controller.

#### 5. Handle Modal Closing

Implement the delegate to close the liveness UI:

```swift
extension MainViewController: YVLivenessViewDelegate {
    func closeModal() {
        DispatchQueue.main.async {
            self.removeLivenessViewController()
        }
    }
}
```

***

### Configuration Options

| Option               | Type     | Required | Description                            | Default | Possible Values |
| -------------------- | -------- | -------- | -------------------------------------- | ------- | --------------- |
| `publicKey`          | String   | Yes      | Your API key for authentication        | -       | Any string      |
| `user`               | Class    | Yes      | User details (requires `firstName`)    | -       | See below       |
| `user.firstName`     | String   | Yes      | User’s first name                      | -       | Any string      |
| `user.lastName`      | String   | No       | User’s last name                       | `nil`   | Any string      |
| `user.email`         | String   | No       | User’s email                           | `nil`   | Any string      |
| `onSuccess`          | Function | No       | Callback on success with liveness data | `nil`   | Any function    |
| `onFailure`          | Function | No       | Callback on failure with error data    | `nil`   | Any function    |
| `sandboxEnvironment` | Bool     | No       | Toggle sandbox mode for testing        | `true`  | `true`, `false` |

***

### Available Tasks

Tasks verify liveness through user interactions. Below are the supported options:

#### Complete The Circle

Users trace a circle with head movements.

| Option       | Type           | Required | Description                 | Default   | Values                      |
| ------------ | -------------- | -------- | --------------------------- | --------- | --------------------------- |
| `difficulty` | TaskDifficulty | No       | Task difficulty             | `.medium` | `.easy`, `.medium`, `.hard` |
| `timeout`    | Number         | No       | Time (ms) before task fails | `nil`     | Any milliseconds            |

Example:

```swift
TaskProperties(task: .completeTheCircle(CompleteTheCircleTask(difficulty: .medium)))
```

#### Yes or No

Users answer questions by tilting their head (right = yes, left = no).

| Option                   | Type           | Required | Description                 | Default   | Values                      |
| ------------------------ | -------------- | -------- | --------------------------- | --------- | --------------------------- |
| `difficulty`             | TaskDifficulty | No       | Task difficulty             | `.medium` | `.easy`, `.medium`, `.hard` |
| `timeout`                | Number         | No       | Time (ms) before task fails | `nil`     | Any milliseconds            |
| `questions`              | Array          | Yes      | List of yes/no questions    | -         | See below                   |
| `questions.question`     | String         | Yes      | Yes/no question             | -         | Any string                  |
| `questions.answer`       | Bool           | Yes      | Correct answer              | -         | `true`, `false`             |
| `questions.errorMessage` | String         | No       | Message on wrong answer     | `nil`     | Any string                  |

Example:

```swift
TaskProperties(task: .yesOrNo(YesOrNoTask(questions: [YesOrNoQuestion(question: "Is 2 + 2 = 7?", answer: false)])))
```

#### Motions

Users perform nods, blinks, or mouth movements.

| Option       | Type           | Required | Description                 | Default   | Values                      |
| ------------ | -------------- | -------- | --------------------------- | --------- | --------------------------- |
| `difficulty` | TaskDifficulty | No       | Task difficulty             | `.medium` | `.easy`, `.medium`, `.hard` |
| `timeout`    | Number         | No       | Time (ms) before task fails | `nil`     | Any milliseconds            |
| `maxNods`    | Int            | No       | Max nods to perform         | `5`       | Any number                  |
| `maxBlinks`  | Int            | No       | Max blinks to perform       | `5`       | Any number                  |

Example:

```swift
TaskProperties(task: .motions(MotionsTaskClass(difficulty: .medium)))
```

***

### Liveness Data

The `onSuccess` callback returns:

| Field          | Type   | Description                     |
| -------------- | ------ | ------------------------------- |
| `faceImage`    | String | User’s face image               |
| `livenessClip` | String | Video of liveness check         |
| `passed`       | Bool   | True if passed, false if failed |
| `metadata`     | Any    | Metadata from initialization    |

The `onFailure` callback returns error details as a string or object, depending on the issue.

***

### Full Example

Here’s the complete implementation from the working code:

```swift
import UIKit
import YouverifyLivenessSDK

class MainViewController: UIViewController {
    private var yvLiveness = YVLiveness(
        publicKey: Environment.shared.value(forKey: "API_KEY") ?? "",
        user: YVLivenessUser(firstName: "Ugochukwu"),
        onSuccess: { data in
            print("The success data is \(data)")
        },
        onFailure: { errorData in
            print("The error data is \(errorData)")
        },
        sandboxEnvironment: false
    )
    
    private var livenessViewController: YVLivenessViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Create a vertical stack view
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 10
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        // Create buttons
        let completeCircleButton = createButton(title: "Complete the Circle", action: #selector(completeTheCircleTapped))
        let yesOrNoButton = createButton(title: "Yes or No", action: #selector(yesOrNoTapped))
        let motionsButton = createButton(title: "Motions", action: #selector(motionsTapped))
        let passiveLivenessButton = createButton(title: "Passive Liveness", action: #selector(passiveLivenessTapped))
        
        // Add buttons to stack view
        stackView.addArrangedSubview(completeCircleButton)
        stackView.addArrangedSubview(yesOrNoButton)
        stackView.addArrangedSubview(motionsButton)
        stackView.addArrangedSubview(passiveLivenessButton)
        
        // Add stack view to the main view
        view.addSubview(stackView)
        
        // Set Auto Layout constraints
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func addLivenessViewController() {
        guard livenessViewController == nil else { return } // Prevent duplicate adds
        let vc = YVLivenessViewController(sdk: yvLiveness)
        vc.delegate = self
        
        addChild(vc)
        
        view.addSubview(vc.view)
        vc.didMove(toParent: self)
        livenessViewController = vc
    }

    private func removeLivenessViewController() {
        guard let vc = livenessViewController else { return }
        vc.willMove(toParent: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParent()
        livenessViewController = nil
    }
    
    // Helper function to create buttons with actions
    private func createButton(title: String, action: Selector) -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle(title, for: .normal)
        button.addTarget(self, action: action, for: .touchUpInside)
        return button
    }
    
    // MARK: - Button Actions
    
    private func startLiveness(with tasks: [TaskProperties]) {
        yvLiveness.startSDK(tasks: tasks)
        DispatchQueue.main.async {
            self.addLivenessViewController()
        }
    }
    
    @objc private func completeTheCircleTapped() {
        startLiveness(with: [
            TaskProperties(task: .completeTheCircle(CompleteTheCircleTask(difficulty: .medium)))
        ])
    }
    
    @objc private func yesOrNoTapped() {
        startLiveness(with: [TaskProperties(
            task: .yesOrNo(YesOrNoTask(questions: [YesOrNoQuestion(question: "Is 2 + 2 = 7?", answer: false)]))
        )])
    }
    
    @objc private func motionsTapped() {
        startLiveness(with: [TaskProperties(
            task: .motions(MotionsTaskClass(difficulty: .medium))
        )])
    }
    
}

extension MainViewController: YVLivenessViewDelegate {
    func closeModal() {
        DispatchQueue.main.async {
            self.removeLivenessViewController()
        }
    }
}
```

### Credits

This SDK is developed and maintained solely by [Youverify](https://youverify.co)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.youverify.co/know-your-customer-services-kyc/sdk/ios-sdk/ios-compat-liveness-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
