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
AppDNA.showScreen("upgrade_prompt") { result in
print("Screen dismissed: \(result.dismissed)")
print("Responses: \(result.responses)")
}
Show a Multi-Screen Flow
AppDNA.showFlow("onboarding_v2") { result in
print("Flow completed: \(result.completed)")
print("Screens viewed: \(result.screensViewed)")
}
Dismiss
Screen Slots (Inline Content)
Place named slots in your SwiftUI views. The console assigns screens to slots.
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:
// 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 only available in #if DEBUG builds.
#if DEBUG
let json = """
{"id":"test","name":"Test","presentation":"modal",
"layout":{"type":"scroll"},"sections":[...]}
"""
AppDNA.previewScreen(json: json, completion: { result in
print("Preview dismissed: \(result.dismissed)")
})
#endif
Signature: previewScreen(json: String, completion: ((ScreenResult) -> Void)? = nil)
Screen Delegate
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
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
}
}
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.