This is the lookup reference for the FlowPilot iOS SDK. Every symbol here is declared public in the SDK source. The guide pages (Getting started through Offline and performance) teach how to use them in context; this page is the index. Internal types are not listed, because you cannot call them.
This reference reflects FlowPilot iOS SDK v1.0.0 (FlowPilotSDK.version). Public symbols can change between versions, so check the version string in your installed package if a signature here does not match.
Setup
Configure the SDK once at launch, then reach everything else through FlowPilot.shared. See Configuration.
// Configure the SDK. Pass a FlowPilotConfiguration. No-ops (logs an error) if
// the API key does not start with "fp_", leaving FlowPilot.shared nil.
public static func configure(_ configuration: FlowPilotConfiguration)
// The shared instance. nil until configure(_:) succeeds.
public private(set) static var shared: FlowPilot?
// Merge new key/value pairs into the SDK context for future resolves.
public func updateContext(_ context: SDKContext)
FlowPilotConfiguration
A public struct. Only apiKey and appId are required; everything else has a default.
| Parameter | Type | Default | Notes |
|---|
apiKey | String | required | Workspace SDK key. Must start with fp_. |
appId | String | required | The app’s FlowPilot App ID. |
environment | FlowPilotEnvironment | .production | Picks the base URL. |
context | SDKContext? | nil | Initial SDK context for variable resolution and targeting. |
cachingEnabled | Bool | true | Disk cache for resolved flows. |
cacheDirectory | String? | nil | Custom flow cache directory. |
resolveTimeout | TimeInterval | 4.0 | Hard wall-clock deadline for a resolve. Clamped to a minimum of 0.5. |
bundledFlows | [String: String] | [:] | Placement key to main-bundle JSON resource name. |
bundledFlowAssets | [String: BundledFlowAssets] | [:] | Offline image/font assets per placement. |
mediaPreloadingEnabled | Bool | true | Preload images in screen order when a session starts. |
imageMemoryCacheSize | Int | 50 * 1024 * 1024 | Image memory cache cap, in bytes (50MB). |
imageDiskCacheSize | Int | 200 * 1024 * 1024 | Image disk cache cap, in bytes (200MB). |
debugMode | Bool? | nil | Debug override. |
logLevel | FlowPilotLogLevel | .error | SDK log verbosity. |
prefetchOnLaunch | [String] | [] | Placement keys to warm in the background after configure(_:). No-op when caching is off. |
prefetchMediaStrategy | PrefetchMediaStrategy | .firstScreen | How aggressively launch prefetch (and prefetch(_:warmMedia: true)) warms images. |
FlowPilotEnvironment
public enum FlowPilotEnvironment: Sendable, Equatable {
case development // https://dev-api.flowpilot.io/v1
case staging // https://staging-api.flowpilot.io/v1
case production // https://api.flowpilot.io/v1
case custom(url: String)
}
FlowPilotLogLevel
public enum FlowPilotLogLevel: Int, Comparable, Sendable {
case none = 0
case error = 1
case warn = 2
case info = 3
case debug = 4
case verbose = 5
}
public enum PrefetchMediaStrategy: Sendable {
case none // JSON + fonts only
case firstScreen // + first-screen & persistent-zone images (default)
case allScreens // + every screen's images
}
Set via FlowPilotConfiguration.prefetchMediaStrategy. Governs launch prefetch (prefetchOnLaunch) and bounds the screen window when a prefetch(_:warmMedia: true) call opts into media warming. See Prefetching.
SDKContext
// Host-provided key/value context for variable resolution and targeting.
public typealias SDKContext = [String: Any]
Presenting
Methods on FlowPilot.shared. The presentPlacement overloads are UIKit-only (#if canImport(UIKit)). See Presenting placements, SwiftUI integration, and Prefetching.
// Resolve and present (UIKit). Returns the result.
@MainActor
public func presentPlacement(
_ placementKey: String,
from viewController: UIViewController,
options: PresentationOptions? = nil
) async throws -> FlowResult
// Resolve and present (UIKit), with a completion closure instead of a return.
@MainActor
public func presentPlacement(
_ placementKey: String,
from viewController: UIViewController,
options: PresentationOptions? = nil,
completion: ((FlowResult) -> Void)? = nil
) async throws
// Never-throwing present (UIKit). Walks the full fallback chain, then presents
// your `fallback` view controller if FlowPilot has nothing to show.
@MainActor
@discardableResult
public func presentPlacement(
_ placementKey: String,
from viewController: UIViewController,
fallback: @escaping @MainActor () -> UIViewController,
options: PresentationOptions? = nil
) async -> FlowResult
// SwiftUI: build a session you drive with FlowPresenterView / .flowPresenter.
@MainActor
public func createSession(
placementKey: String,
additionalContext: SDKContext? = nil,
preloadMedia: Bool? = nil
) async throws -> FlowSession
// SwiftUI: non-throwing counterpart. Returns nil when nothing is presentable.
@MainActor
public func resolveSession(
placementKey: String,
additionalContext: SDKContext? = nil,
preloadMedia: Bool? = nil
) async -> FlowSession?
// True if a flow is cached or resolves for this placement. Not @MainActor.
// Routes through the cache-populating path, so the resolve is not wasted: a
// following present hits the cache.
public func isPlacementReady(_ placementKey: String) async -> Bool
// Warm the cache for one or more placements. Best-effort, concurrent, never
// throws; returns a per-placement outcome map. Pass warmMedia: true to also warm
// images (bounded by prefetchMediaStrategy). @discardableResult.
@discardableResult
public func prefetch(
_ placementKeys: [String],
warmMedia: Bool? = nil
) async -> [String: PrefetchOutcome]
PrefetchOutcome
public struct PrefetchOutcome: Sendable {
public enum State: Sendable {
case warmed(fromCache: Bool) // a presentable flow is cached and ready
case noFlow // resolve ok, backend has nothing to show
case failed(FlowPilotError) // resolve failed; nothing warmed
}
public let placementKey: String
public let state: State
public let mediaWarmed: Bool // first/all-screen images warmed
public var isWarmed: Bool // convenience: true when state is .warmed
}
Returned per placement by prefetch(_:warmMedia:). See Prefetching.
PresentationOptions
public struct PresentationOptions: Sendable {
public let additionalContext: SDKContext?
public let presentationStyle: PresentationStyle?
public let animated: Bool
public init(
additionalContext: SDKContext? = nil,
presentationStyle: PresentationStyle? = nil,
animated: Bool = true
)
}
PresentationStyle
public enum PresentationStyle: String, Sendable {
case fullScreen
case modal
case bottomSheet
}
presentationStyle is accepted by PresentationOptions but is not applied in the current SDK build. Flows present full screen (FlowHostingController forces .fullScreen). Setting .modal or .bottomSheet has no effect today. See Presenting placements.
PlacementInfo
public struct PlacementInfo: Sendable {
public let placementId: String
public let placementKey: String
public let willShow: Bool
public let flowId: String?
public let experimentId: String?
public let variantId: String?
}
PlacementInfo is declared public, but no public API returns it in v1.0.0. To check whether a placement will show, use isPlacementReady(_:) instead.
Session
FlowSession is the live, observable handle to a presented flow. It is a public final class conforming to ObservableObject and Identifiable. See SwiftUI integration, Variables and SDK context, and Media preloading.
Published state
public private(set) var flow: ResolvedFlow
public let placementId: String?
@Published public private(set) var currentScreen: ScreenNode?
@Published public private(set) var isActive: Bool
@Published public private(set) var mediaPreloadProgress: Double // 0.0 ... 1.0
@Published public private(set) var isMediaPreloaded: Bool
public var id: String // Identifiable
currentScreen exposes a ScreenNode whose public fields are id, kind, name, screenType, props, and layout.
Methods
// Variables
public func getVariable(_ key: String) -> VariableValue?
public func getAllVariables() -> [String: VariableValue]
@discardableResult public func setVariable(_ key: String, value: VariableValue) -> Bool
public func resetVariables(sdkContext: SDKContext? = nil)
// Media preloading
public func waitForMediaPreload() async
public func cancelMediaPreload()
// Conversions
public func trackConversion(
amount: Double,
currency: String,
productId: String? = nil,
metadata: [String: Any]? = nil
)
// Lifecycle
public func startNavigation()
public func waitForCompletion() async -> FlowResult
public func dismiss()
public func failPresentation()
public func navigate(to screenNodeId: String)
// Live mirror hot-replace (used by the preview tooling)
@MainActor public func replaceFlow(_ newDefinition: FlowDefinition, fonts: [FontFile]? = nil)
// Preview-only initializer (skips resolution, suppresses analytics)
public convenience init(
flow: ResolvedFlow,
suppressAnalytics: Bool = true,
sdkContext: SDKContext? = nil
)
// Deprecated: use startNavigation() then waitForCompletion()
@available(*, deprecated)
public func start() async -> FlowResult
The engine objects inside a session (variableStore, navigationController, actionExecutor, and so on) are internal. Use the delegating methods above instead of reaching into them.
Presenter (SwiftUI / UIKit)
How a session becomes on-screen UI. See SwiftUI integration.
// SwiftUI view that renders a session.
@MainActor
public struct FlowPresenterView: View {
public init(session: FlowSession)
}
// View modifier: present when the bound session is non-nil, clear it on result.
extension View {
public func flowPresenter(
session: Binding<FlowSession?>,
onResult: ((FlowResult) -> Void)? = nil
) -> some View
}
// UIKit hosting controller that wraps FlowPresenterView (UIKit only).
@MainActor
public class FlowHostingController: UIHostingController<FlowPresenterView> {
public init(session: FlowSession)
public func onCompletion(_ handler: @escaping (FlowResult) -> Void)
}
FlowPresenterModifier (the type behind .flowPresenter) is also public, but use the .flowPresenter(session:onResult:) modifier rather than constructing it directly.
Results
Returned by the presenting and waitForCompletion() APIs. See Results and outcomes.
public struct FlowResult: Sendable {
public let outcome: FlowOutcome
public let finalVariables: [String: VariableValue]
public let screensVisited: [String]
public let durationMs: Int
public let experimentAssignments: [String: String]
public let error: FlowPilotError?
}
public enum FlowOutcome: String, Sendable {
case completed
case dismissed
case error
}
Variables
See Variables and SDK context.
public enum VariableValue: Sendable, Equatable, Codable {
case string(String)
case number(Double)
case boolean(Bool)
case stringList([String])
case numberList([Double])
case booleanList([Bool])
// Typed accessors (coercing where noted)
public var stringValue: String?
public var numberValue: Double? // coerces a numeric string
public var intValue: Int? // coerces a numeric string
public var boolValue: Bool? // coerces "true"/"1"/"yes" and numbers
public var stringListValue: [String]?
public var numberListValue: [Double]?
public var booleanListValue: [Bool]?
// Inspection
public var isEmpty: Bool
public var isString: Bool
public var isNumber: Bool
public var isBoolean: Bool
public var isList: Bool
public var typeName: String
public var displayString: String
}
VariableValue also conforms to ExpressibleBy{String,Integer,Float,Boolean,Array}Literal, so you can write session.setVariable("plan", value: "premium") or value: 3.
public enum VariableType: String, Codable, Sendable {
case string
case number
case boolean
case list
}
// Used by custom components: an input that is either a bound variable or a constant.
public enum ComponentInputValue: Codable, Sendable {
case bind(String)
case value(VariableValue)
}
Custom components and screens
Registration methods on FlowPilot.shared, plus the definition types. See Custom components and Custom screens.
// Register / unregister a custom component by key (+ version, default 1).
public func registerCustomComponent(
key: String,
version: Int = 1,
definition: CustomComponentDefinition
)
public func unregisterCustomComponent(key: String, version: Int = 1)
// Deprecated bare-key overloads (use version 1).
@available(*, deprecated)
public func registerCustomComponent(_ typeId: String, definition: CustomComponentDefinition)
@available(*, deprecated)
public func unregisterCustomComponent(_ typeId: String)
// Custom screens.
public func registerCustomScreen(_ screenId: String, definition: CustomScreenDefinition)
public func unregisterCustomScreen(_ screenId: String)
public struct CustomComponentDefinition: Sendable {
public let inputs: [String: VariableType]?
public let outputs: [String: OutputSchema]?
public let factory: @Sendable @MainActor (CustomComponentProps, CustomComponentContext) -> AnyView
public init(
inputs: [String: VariableType]? = nil,
outputs: [String: OutputSchema]? = nil,
factory: @escaping @Sendable @MainActor (CustomComponentProps, CustomComponentContext) -> AnyView
)
}
public struct OutputSchema: Sendable {
public let description: String?
public let payload: [String: VariableType]?
public init(description: String? = nil, payload: [String: VariableType]? = nil)
}
public struct CustomComponentProps: Sendable {
public let inputs: [String: VariableValue]
// Read-only validated accessors (warn in debug on type mismatch):
public func string(_ key: String) -> String?
public func string(_ key: String, default defaultValue: String) -> String
public func number(_ key: String) -> Double?
public func number(_ key: String, default defaultValue: Double) -> Double
public func bool(_ key: String) -> Bool?
public func bool(_ key: String, default defaultValue: Bool) -> Bool
public func int(_ key: String) -> Int?
public func int(_ key: String, default defaultValue: Int) -> Int
}
public final class CustomComponentContext: @unchecked Sendable {
public let containerSize: CGSize
public let containerConstraints: ContainerConstraints
public func emit(_ eventName: String, payload: [String: Any]? = nil)
public func emit(_ eventName: String)
public struct ContainerConstraints: Sendable {
public let minWidth: Double?
public let maxWidth: Double?
public let minHeight: Double?
public let maxHeight: Double?
public let supportsIntrinsicSize: Bool
}
}
public struct CustomScreenDefinition: Sendable {
public let inputs: [String: VariableType]?
public let outputs: [String: OutputSchema]?
public let factory: @Sendable @MainActor (CustomScreenParams, CustomScreenContext) -> AnyView
public init(/* same shape as CustomComponentDefinition */)
}
public struct CustomScreenParams: Sendable {
public let inputs: [String: VariableValue]
public func string(_ key: String) -> String?
public func string(_ key: String, default defaultValue: String) -> String
public func number(_ key: String) -> Double?
public func number(_ key: String, default defaultValue: Double) -> Double
public func bool(_ key: String) -> Bool?
public func bool(_ key: String, default defaultValue: Bool) -> Bool
}
public final class CustomScreenContext: @unchecked Sendable {
public func emit(_ eventName: String, payload: [String: Any]? = nil)
public func emit(_ eventName: String)
public func setZonesVisible(_ visible: Bool)
@available(*, deprecated) public func setChromeVisible(_ visible: Bool)
}
Custom components render today. Custom screens do not: the SDK accepts registerCustomScreen and stores the definition, but the renderer never looks it up or invokes the factory in v1.0.0. Treat the custom-screen types as a roadmap API. See Custom screens.
Analytics
See Analytics integration and the Event taxonomy.
// Set an additive callback that receives every analytics event the SDK emits.
public func setAnalyticsCallback(_ callback: @escaping AnalyticsCallback)
public typealias AnalyticsCallback = (AnalyticsEvent) -> Void
public struct AnalyticsEvent: Codable, Sendable {
public let eventId: String // wire: event_id
public let appId: String // wire: app_id
public let eventName: String // wire: event_type
public let timestamp: Date
public let placementId: String?
public let flowId: String
public let flowVersionId: String
public let userId: String
public let sessionId: String
public let devicePlatform: String // "ios"
public let sdkVersion: String
public let flowVersion: Int?
public let experimentId: String?
public let variantId: String?
public let variantName: String?
public let screenId: String?
public let screenName: String?
public let screenIndex: Int?
public let elementId: String?
public let elementType: String?
public let interactionType: String?
public let revenue: Double?
public let currency: String?
public let timeSinceFlowStartMs: Int?
public let timeOnScreenMs: Int?
public let appVersion: String?
public let country: String?
public let properties: [String: AnyCodable]?
}
// Events the SDK fires automatically.
public enum AutomaticEventType: String, Sendable {
case flowStarted = "flow_start"
case flowCompleted = "flow_complete"
case flowDismissed = "flow_exit"
case screenViewed = "screen_view"
case screenExited = "screen_exit"
case experimentAssigned = "experiment_exposure"
case elementInteraction = "element_interaction"
}
Conversions are emitted with event_type conversion via trackConversion(...), not through AutomaticEventType.
Errors
See Error handling.
public func setErrorCallback(_ callback: @escaping ErrorCallback)
public typealias ErrorCallback = (FlowPilotError) -> Void
public struct FlowPilotError: Error, Sendable, CustomStringConvertible {
public let code: FlowPilotErrorCode
public let message: String
public let underlyingError: Error?
public let context: [String: String]?
public var description: String
public var localizedDescription: String
public var isClientError: Bool // true for a 4xx status in context
public var isServerError: Bool // true for a 5xx status in context
}
FlowPilotErrorCode is a String enum with these cases (raw values in the Error handling table): invalidApiKey, invalidAppId, sdkNotInitialized, networkError, apiError, timeout, rateLimited, placementNotFound, flowNotFound, targetingNotMet, frequencyLimitReached, unsupportedSchemaVersion, invalidFlowSchema, componentRenderError, customComponentNotFound, customScreenNotFound, navigationError, variableError, actionError, fatalActionError, actionChainTimeout, internalError.
setErrorCallback(_:) stores the closure but the SDK never invokes it in v1.0.0. Observe errors through the thrown FlowPilotError (from the throwing present/create APIs) and through FlowResult.error. See Error handling.
Caching and offline
See Caching and Offline and bundled flows.
// Clear cached flows + fonts + icons.
public func clearCache() async
// Clear cached images + icons.
public func clearImageCache()
// The shared image cache, for advanced use.
public var imageCache: ImageCache { get }
// Register a bundled (offline) default flow for a placement. Four overloads:
public func registerBundledFlow(placementKey: String, json: Data, assets: BundledFlowAssets? = nil)
public func registerBundledFlow(
placementKey: String,
resource: String,
withExtension ext: String = "json",
in bundle: Bundle = .main,
assets: BundledFlowAssets? = nil
)
public func registerBundledFlow(
placementKey: String,
assetBundle subdirectory: String,
in bundle: Bundle = .main,
flowResource: String = "flow",
manifestResource: String = "manifest"
)
public func registerBundledFlowAssets(placementKey: String, assets: BundledFlowAssets)
public final class ImageCache: @unchecked Sendable {
public static let shared: ImageCache
public static let bundledAssetTTL: TimeInterval // effectively non-expiring
public var maxMemoryCacheSize: Int // default 50MB
public var maxDiskCacheSize: Int // default 200MB
public var defaultTTL: TimeInterval // default 7 days
public init(cacheDirectory: String? = nil)
public func getImage(for url: URL) -> UIImage?
public func hasImage(for url: URL) -> Bool
public func setImage(_ image: UIImage, for url: URL, ttl: TimeInterval? = nil)
public func setImageData(_ data: Data, for url: URL, ttl: TimeInterval? = nil)
public func removeImage(for url: URL)
public func clearAll()
public func cleanExpired()
}
public struct BundledFlowAssets: @unchecked Sendable {
// Assets in a plain filesystem directory (manifest + files).
public init(directoryURL: URL, manifest: String = "manifest")
// Assets in an app bundle, optionally inside a folder-reference subdirectory.
public init(bundle: Bundle = .main, subdirectory: String? = nil, manifest: String = "manifest")
}
On non-UIKit platforms (macOS without UIKit), ImageCache is a reduced stub: getImage, setImage, and setImageData are not available there.
Callbacks and typealiases
public typealias AnalyticsCallback = (AnalyticsEvent) -> Void
public typealias ErrorCallback = (FlowPilotError) -> Void
public typealias FlowCompletionCallback = (FlowResult) -> Void
public typealias FlowDismissalCallback = (String) -> Void
FlowCompletionCallback and FlowDismissalCallback are declared public for source compatibility, but there is no setter for them and the SDK does not invoke them in v1.0.0. Get the result from the present APIs’ return value, the completion: closure, or FlowResult instead.
Deprecated aliases
Short-prefix names kept for source compatibility with v1.0 integrators. Each is marked @available(*, deprecated, renamed:); migrate to the FlowPilot* name.
| Deprecated alias | Use instead |
|---|
FPConfiguration | FlowPilotConfiguration |
FPEnvironment | FlowPilotEnvironment |
FPLogLevel | FlowPilotLogLevel |
FPError | FlowPilotError |
FPErrorCode | FlowPilotErrorCode |
Debug
// Draw debug borders around rendered components.
public static var debugBordersEnabled: Bool
// Raise the log level to .verbose (development only).
public func enableDebugOverlay()
// Restore the configured log level.
public func disableDebugOverlay()
// Log user id, session id, environment, and registry counts.
public func logState()
createTestSession() also exists but is compiled only in #if DEBUG builds and is for rendering tests, not production use.
The FlowPilotDelegate protocol
public protocol FlowPilotDelegate: AnyObject {
func flowPilotDidComplete(_ flowId: String, result: FlowResult)
func flowPilotDidDismiss(_ flowId: String)
func flowPilotDidFail(_ error: FlowPilotError)
}
FlowPilotDelegate is declared public, but the SDK has no setter for it and never calls its methods in v1.0.0. It is not usable today. Use the closure-based APIs, the present return values, and the never-throwing overloads instead. See Error handling.
Related pages