> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.withpersona.com/2020-05-18/ios-inline-inquiries/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withpersona.com/_mcp/server. # iOS Inline Inquiries Guide 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`** ```swift swift let inquiry = Inquiry .from(templateId: "itmpl_EXAMPLE", delegate: self) .environment(.sandbox) .build() guard let inlineViewController = inquiry.startInline() else { // Inquiry is no longer valid (already started or completed) return } ``` ### 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`** ```swift swift // In a navigation stack navigationController?.pushViewController(inlineViewController, animated: true) // In a tab or container view controller addChild(inlineViewController) containerView.addSubview(inlineViewController.view) inlineViewController.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`** ```swift swift inlineViewController.navigationItem.title = "Identity Verification" inlineViewController.navigationItem.leftBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(handleBack) ) inlineViewController.navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(handleCancel) ) inlineViewController.delegate = self ``` When the user taps your back or cancel buttons, you can choose to forward the action to the inline view controller: **`swift`** ```swift swift @objc func handleBack() { inlineViewController.navigateBack() } @objc func handleCancel() { inlineViewController.presentCancelModal() } ``` ### 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`** ```swift swift extension MyViewController: InquiryDelegate { func inquiryComplete(inquiryId: String, status: String, fields: [String: InquiryField]) { // User completed the flow. // dismiss inline flow navigationController?.popViewController(animated: true) } func inquiryCanceled(inquiryId: String?, sessionToken: String?) { // User cancelled the flow. // dismiss inline flow navigationController?.popViewController(animated: true) } func inquiryError(_ error: PersonaError) { // An error prevented the user from completing the flow. // dismiss inline flow navigationController?.popViewController(animated: true) } } ``` > **Warning** > > 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`** ```swift swift extension MyViewController: PersonaInlineDelegate { func navigationStateDidUpdate(navigationState: PersonaInlineNavigationState) { backButton?.isEnabled = navigationState.backButtonEnabled cancelButton?.isEnabled = navigationState.cancelButtonEnabled } func onReady() { // Called when the first step is ready to display. // Use this to skip showing a loading state. } } ``` #### 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`** ```swift swift final class MyViewController: UIViewController { private var inlineViewController: PersonaInlineViewController? func startVerification() { let inquiry = Inquiry .from(templateId: "itmpl_EXAMPLE", delegate: self) .build() guard let vc = inquiry.startInline() else { return } vc.delegate = self inlineViewController = vc // Don't push yet — wait for onReady(). } } extension MyViewController: PersonaInlineDelegate { func onReady() { guard let inlineViewController else { return } navigationController?.pushViewController(inlineViewController, animated: true) } } ``` ### 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`** ```swift swift extension MyViewController: InquiryDelegate { func inquiryEventOccurred(event: InquiryEvent) { switch event { case let .start(startEvent): // Flow initialized — startEvent exposes inquiryId and sessionToken. print("Inquiry started: \(startEvent.inquiryId)") case let .pageChange(pageChange): // User moved to a new step or page within the flow. // pageChange.name is the step name configured in your Inquiry Template; // pageChange.path describes the current page within that step. updateProgressIndicator(forStepName: pageChange.name) } } } ``` `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. > A guide on how to launch Inquiries inline.