> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS quickstart

> Configure the SDK, present a placement, and read the result, in UIKit or SwiftUI.

This is the shortest path from an installed SDK to a flow on screen: configure at launch, present a placement, then read the `FlowResult`. It assumes you already have a published flow attached to a placement (the [Quickstart](/get-started/quickstart) walks through building that end to end).

<Info>
  **Before you start**

  You need:

  * A FlowPilot [workspace](/dashboard/workspaces-and-apps).
  * An [app](/dashboard/workspaces-and-apps) inside that workspace.
  * An [SDK API key](/dashboard/api-keys) for that app.
</Info>

You also need:

* The SDK [installed](/ios-sdk/installation) in your Xcode project.
* A placement key (for example `onboarding`) with a published flow attached, targeting iOS.

## Steps

<Steps>
  <Step title="Configure at launch">
    Call `FlowPilot.configure(_:)` once, as early as possible, with your API key and App ID. See [Configuration](/ios-sdk/configuration) for every option.

    ```swift theme={null}
    import FlowPilotSDK

    // In your App struct's init() or AppDelegate.application(_:didFinishLaunchingWithOptions:)
    FlowPilot.configure(
        FlowPilotConfiguration(
            apiKey: "fp_live_xxxxxxxxxxxxxxxx",
            appId: "your-app-id"
        )
    )
    ```
  </Step>

  <Step title="Present a placement">
    Ask the SDK to resolve and present your placement. Reach the SDK through `FlowPilot.shared`.

    <Tabs>
      <Tab title="UIKit">
        Call `presentPlacement(_:from:)` from a view controller. It is `async` and `throws`, so wrap it in a `Task` and a `do/catch`.

        ```swift theme={null}
        import UIKit
        import FlowPilotSDK

        final class HomeViewController: UIViewController {
            override func viewDidAppear(_ animated: Bool) {
                super.viewDidAppear(animated)

                Task {
                    guard let flowPilot = FlowPilot.shared else { return }
                    do {
                        let result = try await flowPilot.presentPlacement(
                            "onboarding",
                            from: self
                        )
                        handle(result)
                    } catch {
                        print("FlowPilot could not present a flow: \(error)")
                    }
                }
            }
        }
        ```
      </Tab>

      <Tab title="SwiftUI">
        Create a `FlowSession` with `createSession(placementKey:)` and present it with the `.flowPresenter(session:onResult:)` view modifier. The session binding drives presentation: set it to start, and the SDK clears it when the flow ends.

        ```swift theme={null}
        import SwiftUI
        import FlowPilotSDK

        struct OnboardingGate: View {
            @State private var session: FlowSession?

            var body: some View {
                Color.clear
                    .flowPresenter(session: $session) { result in
                        handle(result)
                    }
                    .task {
                        session = try? await FlowPilot.shared?.createSession(
                            placementKey: "onboarding"
                        )
                    }
            }
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Read the result">
    Both paths hand you a `FlowResult`. Switch on its `outcome` to decide what to do next.

    ```swift theme={null}
    func handle(_ result: FlowResult) {
        switch result.outcome {
        case .completed:
            // The user finished the flow.
            print("Completed")
        case .dismissed:
            // The user closed the flow before finishing.
            print("Dismissed")
        case .error:
            // FlowPilot had nothing to present, or rendering failed.
            print("Error: \(result.error?.message ?? "unknown")")
        }
    }
    ```

    `FlowResult` also carries `finalVariables`, `screensVisited`, `durationMs`, and `experimentAssignments`. See [Results and outcomes](/ios-sdk/results-and-outcomes).
  </Step>
</Steps>

That is the full loop: configure, present, read the result. For options, the never-throws host fallback, prefetching, and the SwiftUI surface in depth, see the pages below.

## Common mistakes

* **Using a dashboard token instead of an SDK API key.** The SDK authenticates with `Authorization: Bearer <sdk-key>`. Your dashboard login will not work. Create an SDK key in the app settings.
* **App ID mismatch.** The `appId` you configure must own the placement, or the resolve returns no flow.
* **Placement key typo, paused placement, or no flow attached.** The key in code must match the placement key exactly, the placement must be active, and a published flow must be attached.
* **Forgetting to publish.** A draft flow is never served. Publish a version and attach it to the placement.

## Troubleshooting

Nothing appears? Walk this checklist:

1. Is the **API key** an SDK key (it starts with `fp_`) and valid for the environment you configured?
2. Is the **App ID** the one that owns the placement?
3. Is the **placement** active (not paused) and targeting iOS?
4. Is a **published** flow version attached to the placement?
5. Is the **placement key** in your code an exact match?
6. Is the device **online**? If it may be offline, configure caching or a [bundled fallback](/ios-sdk/offline-bundled-flows).

Still stuck? See [iOS troubleshooting](/ios-sdk/troubleshooting).

## Related pages

* [Presenting placements](/ios-sdk/presenting-placements)
* [SwiftUI](/ios-sdk/swiftui)
* [Results and outcomes](/ios-sdk/results-and-outcomes)
* [Configuration](/ios-sdk/configuration)
* [Quickstart (end to end)](/get-started/quickstart)
