Turning preloading on or off
Preloading is on by default, controlled in two places:Set in
FlowPilotConfiguration. The default for every session. Images and media are downloaded in screen order when a flow initializes.A per-session override on
createSession(placementKey:additionalContext:preloadMedia:). Pass true or false to override the configuration for one session; leave it nil to inherit the configuration default.Observing progress
AFlowSession publishes two read-only properties you can observe (it is an ObservableObject):
Download progress from
0.0 to 1.0.true once preloading has finished (or was disabled). It becomes true even if some downloads failed, so it means “preloading is done”, not “everything succeeded”.waitForMediaPreload()isasyncand suspends until preloading completes. Awaiting it does not block a thread, it suspends the calling task. Use it to gate a present on a fully preloaded flow.cancelMediaPreload()stops an in-progress preload, for example if the user navigates away before you present.
Example
Gate the present on a fully preloaded flow
Create the session (which starts preloading), wait for it, then present. The short upfront wait buys a flow with no mid-flow loading states:Show a loading bar bound to progress (SwiftUI)
ObservemediaPreloadProgress on the session and swap to the flow once isMediaPreloaded is true:
Notes
- Preloading is in screen order. Images on the first screen download first, then the second, and so on. Persistent zones (navigation bar, footer, overlay) are highest priority because they show on every screen. So even a partial preload covers what the user sees first.
- It only covers images. The preloader downloads image content. It does not preload Lottie animations or other media types.
- Gating is a trade-off. Waiting for a full preload means a short, predictable delay before the flow appears, in exchange for no spinners during the flow. Not gating means the flow appears immediately and the last screens may still be loading. Pick per placement.
- Prefetch is related but separate. A bare
prefetchwarms the resolved-flow cache and fonts, not images; this media preloader runs at session creation or present time. They are complementary. You can also warm images ahead of time withprefetch(_:warmMedia: true)orprefetchOnLaunch(bounded byprefetchMediaStrategy), which uses this same image cache, so a placement warmed that way shows its first screen with no pop-in.
Common mistakes
- Blocking the main thread to wait. Do not spin on
isMediaPreloadedin a loop or run preloading synchronously on the main actor. Useawait session.waitForMediaPreload()(which suspends, not blocks) or observemediaPreloadProgressfrom SwiftUI. - Gating heavy media on cellular. A flow with many large images can be slow to fully preload on a cellular connection. If you gate the present, the user waits. Consider not gating, or trimming media, for flows that run on first launch.
- Assuming
isMediaPreloadedmeans success. It flips totruewhen preloading finishes, even if some images failed to download. A failed image simply loads (or retries) on demand later; it does not block the flow. - Recreating the session to retry preloading. Preloading starts once per session at creation. To re-run it, create a new session rather than calling preload methods again.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Images still flash a placeholder | Preloading disabled, or the present is not gated | Set mediaPreloadingEnabled: true, or await waitForMediaPreload() before presenting |
| Long delay before the flow appears | Gating a present on a flow with heavy media | Drop the gate, or reduce image sizes in the flow |
mediaPreloadProgress never reaches 1.0 | Some images failed to download | Expected; isMediaPreloaded still flips to true and those images load on demand. Check the network and the image URLs |
| UI freezes while preparing | Waiting on the main thread incorrectly | Move the wait into a Task and await it |