For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Help CenterOpenAPI SpecStatus
OverviewInquiriesTransactionsRelaysAPI ReferenceChangelog
OverviewInquiriesTransactionsRelaysAPI ReferenceChangelog
  • Overview
    • Inquiries Overview
    • Inquiry Model Lifecycle
    • Inquiry Templates
  • Managing inquiries
    • Creating Inquiries
    • Resuming Inquiries
    • Accessing Inquiry status and data
  • Hosted Flow integration
  • Embedded Flow integration
  • Mobile SDK integration
    • Overview
      • Tutorial: Pre-create inquiries for iOS
      • iOS Integration Guide
      • iOS Inline Inquiries Guide
      • iOS Changelog
      • iOS Privacy Manifest
      • Migrate iOS Theming from Client to Server
      • Migrate from iOS SDK v1 to v2
      • iOS Licenses
    • Webview
  • Troubleshooting
    • Troubleshooting Steps
    • Troubleshooting Common Issues
LogoLogo
Help CenterOpenAPI SpecStatus
On this page
  • Building an inline Inquiry
  • Presenting the view controller
  • Getting results from an inline Inquiry
  • Using custom navigation UI
  • Loading state handling
  • Tracking flow progress with events
Mobile SDK integrationiOS

iOS Inline Inquiries Guide

A guide on how to launch Inquiries inline.
Was this page helpful?
Previous

iOS Changelog

Changes, Fixes, and Features for our iOS SDK.
Next
Built with

Persona inquiries are usually presented fullscreen on iOS, but it is possible to launch inquiries inline using the SDK. The inline flow lets you embed the Persona Inquiry directly into your app’s navigation. Your app controls the presentation, navigation actions (back/cancel), and dismissal of Persona.

Building an inline Inquiry

To launch an inquiry inline, build one via same builders as you normally would. Then call startInline() on the built Inquiry to get a PersonaInlineViewController. This viewController is detached from the view hierarchy. startInline() returns nil if the Inquiry has already been started or completed.

swift
1let inquiry = Inquiry
2 .from(templateId: "itmpl_EXAMPLE", delegate: self)
3 .environment(.sandbox)
4 .build()
5
6guard let inlineViewController = inquiry.startInline() else {
7 // Inquiry is no longer valid (already started or completed)
8 return
9}

Presenting the view controller

The inline view controller can be presented like any other UIViewController. How you present it depends on your app’s architecture.

swift
1// In a navigation stack
2navigationController?.pushViewController(inlineViewController, animated: true)
3
4// In a tab or container view controller
5addChild(inlineViewController)
6containerView.addSubview(inlineViewController.view)
7inlineViewController.didMove(toParent: self)

By default, the SDK’s built-in navigation bar is hidden so that you can provide your own. Configure title and bar buttons on the view controller’s navigationItem:

swift
1inlineViewController.navigationItem.title = "Identity Verification"
2inlineViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(
3 image: UIImage(systemName: "chevron.backward"),
4 style: .plain,
5 target: self,
6 action: #selector(handleBack)
7)
8inlineViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(
9 image: UIImage(systemName: "xmark"),
10 style: .plain,
11 target: self,
12 action: #selector(handleCancel)
13)
14inlineViewController.delegate = self

When the user taps your back or cancel buttons, you can choose to forward the action to the inline view controller:

swift
1@objc func handleBack() {
2 inlineViewController.navigateBack()
3}
4
5@objc func handleCancel() {
6 inlineViewController.presentCancelModal()
7}

Getting results from an inline Inquiry

Inline Inquiries return results through the same InquiryDelegate protocol used by the modal flow. Implement it on the same object you passed as delegate: when constructing the Inquiry.

swift
1extension MyViewController: InquiryDelegate {
2 func inquiryComplete(inquiryId: String, status: String, fields: [String: InquiryField]) {
3 // User completed the flow.
4
5 // dismiss inline flow
6 navigationController?.popViewController(animated: true)
7 }
8
9 func inquiryCanceled(inquiryId: String?, sessionToken: String?) {
10 // User cancelled the flow.
11
12 // dismiss inline flow
13 navigationController?.popViewController(animated: true)
14 }
15
16 func inquiryError(_ error: PersonaError) {
17 // An error prevented the user from completing the flow.
18
19 // dismiss inline flow
20 navigationController?.popViewController(animated: true)
21 }
22}

Unlike the fullscreen presentation flow, the inline flow is not automatically dismissed on completion. Your app is responsible for removing or popping the inline view controller from its container.

Using custom navigation UI

Because the SDK does not render its own navigation bar in the inline flow, your app needs to keep its buttons in sync with the SDK’s internal state if you wish to allow users to navigate within Persona’s flow. Inquiries can have complex states where the back and/or cancel actions are not available, and your UI should reflect this.

Implement PersonaInlineDelegate to receive navigation state updates and a one-time signal when the first step is ready to display:

swift
1extension MyViewController: PersonaInlineDelegate {
2 func navigationStateDidUpdate(navigationState: PersonaInlineNavigationState) {
3 backButton?.isEnabled = navigationState.backButtonEnabled
4 cancelButton?.isEnabled = navigationState.cancelButtonEnabled
5 }
6
7 func onReady() {
8 // Called when the first step is ready to display.
9 // Use this to skip showing a loading state.
10 }
11}

Loading state handling

By default there is a brief loading period before the first step renders. You can either show your own loading UI while the SDK initializes, or hold onto the inline view controller and only present it once onReady() is called to avoid a visible loading delay.

swift
1final class MyViewController: UIViewController {
2 private var inlineViewController: PersonaInlineViewController?
3
4 func startVerification() {
5 let inquiry = Inquiry
6 .from(templateId: "itmpl_EXAMPLE", delegate: self)
7 .build()
8 guard let vc = inquiry.startInline() else { return }
9 vc.delegate = self
10 inlineViewController = vc
11 // Don't push yet — wait for onReady().
12 }
13}
14
15extension MyViewController: PersonaInlineDelegate {
16 func onReady() {
17 guard let inlineViewController else { return }
18 navigationController?.pushViewController(inlineViewController, animated: true)
19 }
20}

Tracking flow progress with events

Use InquiryDelegate.inquiryEventOccurred(event:) to react to flow progression — for example, to update a progress indicator, breadcrumb, or analytics event as the user moves between steps.

swift
1extension MyViewController: InquiryDelegate {
2 func inquiryEventOccurred(event: InquiryEvent) {
3 switch event {
4 case let .start(startEvent):
5 // Flow initialized — startEvent exposes inquiryId and sessionToken.
6 print("Inquiry started: \(startEvent.inquiryId)")
7
8 case let .pageChange(pageChange):
9 // User moved to a new step or page within the flow.
10 // pageChange.name is the step name configured in your Inquiry Template;
11 // pageChange.path describes the current page within that step.
12 updateProgressIndicator(forStepName: pageChange.name)
13 }
14 }
15}

pageChange.name is the step name you configured in your Inquiry Template, so the strings you compare against should match the step names from your own template (not the SDK’s internal step types). Flow completion is signaled by inquiryComplete, not by a pageChange event.