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

# Server-Driven Screens

> Display any screen designed in the console, rendered natively

<Info>
  **Supported on:** iOS SDK `1.0.61+`
</Info>

## Overview

Server-driven screens let growth teams design and deploy native screens from the AppDNA console -- no app release needed. The SDK renders screens from JSON config fetched via Firestore.

Screens combine content blocks from any SDK module (onboarding, paywalls, surveys, messages) into a single composable surface. You can build feature announcements, upgrade prompts, referral screens, guided tutorials, and more -- all without writing native UI code.

## Show a Screen

```swift theme={null}
AppDNA.showScreen("upgrade_prompt") { result in
    print("Screen dismissed: \(result.dismissed)")
    print("Responses: \(result.responses)")
}
```

## Show a Multi-Screen Flow

```swift theme={null}
AppDNA.showFlow("onboarding_v2") { result in
    print("Flow completed: \(result.completed)")
    print("Screens viewed: \(result.screensViewed)")
}
```

## Dismiss

```swift theme={null}
AppDNA.dismissScreen()
```

## Screen Slots (Inline Content)

Place named slots in your SwiftUI views. The console assigns screens to slots.

```swift theme={null}
struct HomeView: View {
    var body: some View {
        VStack {
            AppDNAScreenSlot("home_hero")
            // ... your app content ...
            AppDNAScreenSlot("home_bottom")
        }
    }
}
```

* Empty slots render nothing (no visual impact)
* Content updates on next config refresh
* Supports audience targeting per slot

## Navigation Interception

Automatically inject screens between app navigations:

```swift theme={null}
// Enable (one-time setup)
AppDNA.enableNavigationInterception()

// Or for specific screens only
AppDNA.enableNavigationInterception(forScreens: ["SettingsVC", "Premium*"])

// Disable
AppDNA.disableNavigationInterception()
```

## Debug Preview

Test screens from raw JSON without publishing to Firestore. `previewScreen` is a plain `public static` method — it is available in all build configurations, not just `#if DEBUG`.

```swift theme={null}
let json = """
{"id":"test","name":"Test","presentation":"modal",
 "layout":{"type":"scroll"},"sections":[...]}
"""
let presented = AppDNA.previewScreen(json: json, completion: { result in
    print("Preview dismissed: \(result.dismissed)")
})
```

It returns `Bool` — `true` if the JSON parsed and a screen was presented. Signature: `@discardableResult previewScreen(json: String, completion: ((ScreenResult) -> Void)? = nil) -> Bool`

## Screen Delegate

All 4 methods on `AppDNAScreenDelegate` fire from `ScreenManager` alongside the existing analytics events:

* `onScreenPresented(screenId:)` — fires when a screen appears (alongside the `screen_presented` event).
* `onScreenDismissed(screenId:, result:)` — fires when a screen is dismissed; `ScreenResult` carries the dismissal duration and any responses captured during the screen.
* `onFlowCompleted(flowId:, result:)` — fires when a multi-screen flow ends. `FlowResult.completed` is `true` if the user reached the final screen, `false` if they abandoned partway through.
* `onScreenAction(screenId:, action:)` — fires for every section action the user takes on a screen. Return `true` to let the SDK process the action normally; return `false` to suppress default handling.

```swift theme={null}
class MyDelegate: AppDNAScreenDelegate {
    func onScreenPresented(screenId: String) { }
    func onScreenDismissed(screenId: String, result: ScreenResult) { }
    func onFlowCompleted(flowId: String, result: FlowResult) { }
    func onScreenAction(screenId: String, action: SectionAction) -> Bool { true }
}

AppDNA.screenDelegate = MyDelegate()
```

## Presentation Modes

Screens support four presentation modes configured in the Console:

| Mode           | Description                                      |
| -------------- | ------------------------------------------------ |
| `fullscreen`   | Full-screen modal covering the entire screen     |
| `modal`        | Standard modal sheet presentation                |
| `bottom_sheet` | Draggable bottom sheet                           |
| `push`         | Navigation push (within a navigation controller) |

## Section Types

Screens are composed of ordered sections. The unified section registry includes content from all SDK modules:

| Category   | Section Types                                                                  |
| ---------- | ------------------------------------------------------------------------------ |
| Generic    | `content_blocks`, `hero`, `spacer`, `divider`, `cta_footer`, `sticky_footer`   |
| Onboarding | `onboarding_step`, `progress_indicator`, `navigation_controls`                 |
| Paywall    | `paywall_header`, `paywall_plans`, `paywall_cta`, `paywall_features`, and more |
| Survey     | `survey_question`, `survey_nps`, `survey_csat`, `survey_rating`                |
| Message    | `message_banner`, `message_modal`, `message_content`                           |
| Media      | `image_section`, `video_section`, `lottie_section`, `rive_section`             |

## Auto-Tracked Events

| Event                    | When                   |
| ------------------------ | ---------------------- |
| `screen_presented`       | Screen appears         |
| `screen_dismissed`       | Screen disappears      |
| `screen_action`          | User taps a CTA        |
| `flow_started`           | Flow begins            |
| `flow_completed`         | Flow finishes          |
| `flow_abandoned`         | Flow dismissed early   |
| `slot_rendered`          | Slot displays content  |
| `slot_registered`        | Slot first renders     |
| `interception_triggered` | Nav interception fires |

## Full Example

```swift theme={null}
import AppDNASDK

class ScreenCoordinator: AppDNAScreenDelegate {
    init() {
        AppDNA.screenDelegate = self
    }

    func showUpgradePrompt() {
        AppDNA.showScreen("upgrade_prompt") { result in
            if result.responses["purchased"] as? Bool == true {
                self.unlockPremium()
            }
        }
    }

    func startFeatureTour() {
        AppDNA.showFlow("feature_tour_v2") { result in
            print("Tour completed: \(result.completed)")
        }
    }

    // MARK: - AppDNAScreenDelegate

    func onScreenPresented(screenId: String) {
        print("Screen shown: \(screenId)")
    }

    func onScreenDismissed(screenId: String, result: ScreenResult) {
        print("Screen dismissed: \(screenId)")
    }

    func onFlowCompleted(flowId: String, result: FlowResult) {
        print("Flow completed: \(flowId), screens viewed: \(result.screensViewed)")
    }

    func onScreenAction(screenId: String, action: SectionAction) -> Bool {
        // Return false to prevent default action handling
        return true
    }
}
```

<Note>
  Screens are delivered via the same Firestore config bundle as other SDK modules. Ensure your app has network access on first launch to fetch the latest screen configurations.
</Note>
