Generate Native SwiftUI from LLM Responses: Server‑Driven Rebooking & Refund Flows with Div‑Based Layouts

When a traveler opens your airline or OTA app after a disruption, their first question is: “What are my options now?”

Layered architectural corridor model with multiple overlapping pathways symbolizing rebooking and refund options in server-driven SwiftUI layouts

When a traveler opens your airline or OTA app after a disruption, their first question is: “What are my options now?”

If your AI assistant can generate native iOS UI from LLM responses, that question shouldn’t be answered with a paragraph of text. It should be answered with a clear, actionable, server‑driven SwiftUI screen that lays out rebooking options, refund eligibility, and support paths in one high‑trust surface.

This guide shows how to design those disruption flows using div‑based layouts as the backbone for media‑heavy, AI‑generated interfaces.

Why SwiftUI + server‑driven layouts are ideal for disruption flows

Flight disruption is not an edge case.

  • In 2024, U.S. carriers achieved a 78.1% on‑time arrival rate and a 1.4% cancellation rate across the year (BTS, Air Travel Consumer Report, 2024 annual, accessed 2026‑09‑04).
  • That means millions of travelers hit a “what now?” moment each year.

At the same time:

  • 71% of passengers book online or via mobile app and 53% prefer the airline’s website/app (IATA Global Passenger Survey 2024, accessed 2026‑09‑04).
  • Only 16% prefer human interaction (IATA 2024, accessed 2026‑09‑04).

A native SwiftUI + server‑driven stack lets you:

  • Centralize disruption logic on the server.
  • Stream AI‑composed layouts as div trees.
  • Render fully native SwiftUI screens that match your design system.

The result: an AI assistant that orchestrates UI, not just text.

Best tools to turn LLM responses into native iOS interfaces

Today’s options for turning LLM responses into native iOS interfaces fall into three broad categories:

  1. Custom SDUI frameworks (home‑grown JSON → SwiftUI systems).
  2. Generic server‑driven UI iOS frameworks like DivKit.
  3. AI‑aware interface layers like the Uzori iOS SDK UI framework.

If your goal is to generate native SwiftUI from LLM responses, you’ll want:

  • A typed schema for layouts (divs/blocks).
  • Server‑side validation and safety checks.
  • Streaming support for progressive rendering.
  • A clear mapping to SwiftUI components.

Uzori sits at this intersection:

  • The SDK integrates as a single SwiftUI screen.
  • LLMs compose screen trees in a constrained schema.
  • Your server validates and streams SwiftUI‑ready divs directly into the app.

We’ll use Uzori’s approach as the reference pattern, but the architectural concepts apply to any server‑driven UI iOS development stack.

Why divs power server‑driven SwiftUI and AI‑generated disruption layouts

A “div” in this context is a generic content block primitive:

  • Think of it as a JSON representation of a view.
  • Blocks can be composed: stacks, cards, images, labels, buttons, etc.
  • Each block has typed properties and children.

For disruption flows, divs are ideal because they:

  • Support media‑heavy layouts (route maps, aircraft imagery, badges).
  • Map cleanly to SwiftUI (VStack, HStack, ZStack, ScrollView).
  • Are easy for LLMs to compose and modify as tree structures.
  • Are easy for your backend to validate and sanitize.

We’ll focus on four core primitives:

  • stack – vertical/horizontal layout container.
  • card – visual grouping for a single option.
  • media – image/video with metadata.
  • action – tappable behavior (rebook, refund, contact support).

Core div schema for disruption: JSON Schema example

To support server‑validated SwiftUI screens streaming from LLM output, define a strong JSON Schema for your div primitives.

Below is a compact example (simplified for readability):

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schema/disruption-screen.json",
"title": "DisruptionScreen",
"type": "object",
"required": ["type", "children"],
"properties": {
"type": { "const": "screen" },
"version": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#/definitions/div" },
"maxItems": 50
}
},
"definitions": {
"div": {
"type": "object",
"required": ["type"],
"properties": {
"id": { "type": "string", "maxLength": 64 },
"type": { "type": "string" },
"spacing": { "type": "number", "minimum": 0, "maximum": 64 },
"padding": { "type": "number", "minimum": 0, "maximum": 48 },
"stack": { "$ref": "#/definitions/stack" },
"card": { "$ref": "#/definitions/card" },
"media": { "$ref": "#/definitions/media" },
"text": { "$ref": "#/definitions/text" },
"action": { "$ref": "#/definitions/action" },
"children": {
"type": "array",
"items": { "$ref": "#/definitions/div" },
"maxItems": 20
}
},
"oneOf": [
{ "properties": { "type": { "const": "stack" }, "stack": {"$ref": "#/definitions/stack"} } },
{ "properties": { "type": { "const": "card" }, "card": {"$ref": "#/definitions/card"} } },
{ "properties": { "type": { "const": "media" }, "media": {"$ref": "#/definitions/media"} } },
{ "properties": { "type": { "const": "text" }, "text": {"$ref": "#/definitions/text"} } },
{ "properties": { "type": { "const": "action" },"action":{"$ref": "#/definitions/action"} } }
]
},
"stack": {
"type": "object",
"required": ["direction"],
"properties": {
"direction": { "enum": ["vertical", "horizontal", "overlap"] },
"alignment": { "enum": ["leading", "center", "trailing", "fill"] }
}
},
"card": {
"type": "object",
"required": ["variant"],
"properties": {
"variant": { "enum": ["flightOption", "info", "warning"] },
"elevation": { "type": "number", "minimum": 0, "maximum": 8 }
}
},
"media": {
"type": "object",
"required": ["kind", "url"],
"properties": {
"kind": { "enum": ["image", "video"] },
"url": {
"type": "string",
"format": "uri",
"pattern": "^https://media.example.com/"
},
"aspectRatio": { "type": "number", "minimum": 0.5, "maximum": 2.0 },
"alt": { "type": "string", "maxLength": 160 }
}
},
"text": {
"type": "object",
"required": ["style", "value"],
"properties": {
"style": { "enum": ["title", "subtitle", "body", "caption", "badge"] },
"value": { "type": "string", "maxLength": 512 },
"dataKey": { "type": "string", "maxLength": 64 }
}
},
"action": {
"type": "object",
"required": ["kind", "target"],
"properties": {
"kind": { "enum": ["rebook", "refund", "supportChat", "call"] },
"target": { "type": "string", "maxLength": 128 },
"parameters": {
"type": "object",
"additionalProperties": { "type": ["string", "number", "boolean"] }
}
}
}
}
}

Key validation rules baked into this schema:

  • Block count limits (maxItems) to stop pathological trees.
  • Media URL whitelisting (pattern for your CDN domain).
  • Text length limits to keep layouts stable.
  • Action allowlist via kind enum.

This is the foundation for server‑validated SwiftUI screens.

Mapping a div JSON tree to SwiftUI components

Once the server has validated a div tree, your iOS app can map each node to SwiftUI.

Example: flightCard div → SwiftUI view

Imagine a disruption layout where each option is a card representing a possible rebooking or refund.

Sample JSON snippet:

{
"type": "card",
"id": "option-1",
"card": { "variant": "flightOption", "elevation": 2 },
"children": [
{
"type": "stack",
"stack": { "direction": "horizontal", "alignment": "center" },
"children": [
{
"type": "text",
"text": { "style": "title", "value": "Rebook via JFK", "dataKey": "option.title" }
},
{
"type": "text",
"text": { "style": "badge", "value": "Arrive +3h" }
}
]
},
{
"type": "media",
"media": {
"kind": "image",
"url": "https://media.example.com/routes/jfk-lhr.png",
"alt": "Route map via JFK"
}
},
{
"type": "action",
"action": {
"kind": "rebook",
"target": "rebookingFlow",
"parameters": { "optionId": "opt_123" }
}
}
]
}

SwiftUI pseudo‑renderer:

struct DivView: View {
let node: DivNode
let actionHandler: (DivAction) -> Void

var body: some View {
switch node.type {
case .stack(let config):
stackView(config: config, children: node.children)
case .card(let config):
CardView(config: config, children: node.children, actionHandler: actionHandler)
case .text(let text):
textView(text)
case .media(let media):
mediaView(media)
case .action(let action):
Button { actionHandler(action) } label: {
Text(label(for: action.kind))
}
}
}

@ViewBuilder
private func stackView(config: StackConfig, children: [DivNode]) -> some View {
switch config.direction {
case .vertical:
VStack(alignment: config.alignment.toSwiftUI, spacing: node.spacing) {
ForEach(children) { DivView(node: $0, actionHandler: actionHandler) }
}
case .horizontal:
HStack(alignment: .center, spacing: node.spacing) {
ForEach(children) { DivView(node: $0, actionHandler: actionHandler) }
}
case .overlap:
ZStack {
ForEach(children) { DivView(node: $0, actionHandler: actionHandler) }
}
}
}
}

Action routing and state transitions:

func handleAction(_ action: DivAction) {
switch action.kind {
case .rebook:
navigation.push(.rebookingFlow(optionId: action.parameters["optionId"] as? String))
case .refund:
navigation.push(.refundFlow(bookingId: action.parameters["bookingId"] as? String))
case .supportChat:
openSupportChat(ticketId: action.parameters["ticketId"] as? String)
case .call:
callSupport(number: action.target)
}
}

This pattern generalizes across all your AI‑driven rebooking suggestions in native iOS.

Designing rebooking & refunds UI patterns for mobile disruption flows

The shape of a disruption answer should be a single decision surface that combines:

  • Status – what changed, why, and when.
  • Rebooking options – alternative flights, times, routes.
  • Refund eligibility – when the traveler can get money back.
  • Support – escalation paths if options don’t fit.

1. Lead with a disruption summary card

Use a card with stacked text blocks and optional media:

  • Highlight the disruption plainly: “Your flight AA123 was cancelled.”
  • Show operational context: departure/arrival airports, original time, new status.
  • Optionally include a route map image via media for clarity.

This is where you can embed references to regulatory context.

  • In the U.S., the DOT’s 2024 automatic refund rule requires refunds when a carrier cancels or makes a significant change (3+ hours domestic, 6+ hours international) and the traveler declines alternatives (U.S. DOT, Final Rule on Refunds, April 2024, accessed 2026‑09‑04; see also DOT passenger explainer, 2024‑04‑24).
  • In the EU, air passenger rights already include reimbursement, rerouting, and assistance for cancellations and long delays, strengthened by 2026 updates (EU passenger rights portal, accessed 2026‑09‑04).

LLMs can summarize the rule into user‑friendly text within your div text blocks, but the rule logic remains server‑side.

2. Present rebooking options as cards, not chat paragraphs

Each rebooking option is a card:

  • text – route, departure/arrival times, total delay vs. original.
  • badge text – “No extra cost”, “Arrive +3h”, “Overnight connection”.
  • media – carrier logo, aircraft type, or route map.
  • action – kind: "rebook" hooked into your backend.

LLMs can propose multiple options; your backend:

  • Filters by fare rules and availability.
  • Applies priority logic (earliest arrival, least connections, same day vs next day).
  • Scores and orders cards before sending the div tree.

3. Make refund options visible and compliant

Given the DOT’s automatic refund rule (in force for covered U.S. flights since 2024, see DOT Final Rule, accessed 2026‑09‑04), your disruption screen should:

  • Show a “Request refund” card when the traveler is eligible and declines rebooking.
  • Clarify timing: refunds must be processed within 7 business days for credit card payments and 20 calendar days for other payment methods (DOT Final Rule, 2024, accessed 2026‑09‑04).
  • Surface ancillary refunds (checked bags, seat fees) when applicable.

Wrap this in a card with:

  • text blocks explaining eligibility.
  • action with kind: "refund" → your refund API.

4. Provide support paths for edge cases

Not everyone wants self‑service.

  • IATA’s 2024 survey shows 16% of travelers still prefer human interaction (IATA GPS 2024, accessed 2026‑09‑04).

Use a dedicated card for support:

  • text – “Need help with special assistance, groups, or multi‑ticket journeys?”
  • action – kind: "supportChat" or kind: "call" with guarded targets.

5. Present decisions as clear, mutually exclusive choices

Avoid UI that forces users to parse policy.

Instead, structure your div tree so the final row of the disruption screen looks like:

  • Card A: “Keep current itinerary” (if still operating) with a rebook or accept action.
  • Card B: “Choose a different flight” with a rebook action.
  • Card C: “Request a refund instead” with a refund action.

Each card is visually distinct, and tapping one drives a single, unambiguous flow. This structure is much easier for LLMs to generate and for your backend to validate than arbitrary text.

Server‑driven UI iOS frameworks vs AI interface layers beyond chat

Most teams building non‑chat AI interface design patterns for disruption flows start from a server‑driven UI foundation and layer AI on top.

Classic SDUI stack

  • Server returns JSON layout for each screen.
  • iOS renders it as SwiftUI or UIKit.
  • No AI involvement — layouts are hand‑coded templates.

AI interface layer pattern

  • LLMs propose layouts in a typed div schema.
  • Server validates against schema and business rules.
  • Validated layouts stream to iOS as server‑driven SwiftUI.

This is where Uzori positions itself: an AI‑native interface layer that does not break existing flows because it lives inside your SDUI architecture.

Uzori vs DivKit: feature, stability and quality comparison

Both Uzori and DivKit are relevant if you care about server‑driven UI iOS frameworks. They differ in focus and capabilities.

Uzori vs DivKit comparison (high level)

  • Primary goal — Uzori iOS SDK (SwiftUI): Generate native SwiftUI from LLM responses and backend schemas; DivKit (Yandex / community): Generic SDUI rendering engine for iOS/Android/Web
  • Platform focus — Uzori iOS SDK (SwiftUI): Native iOS, SwiftUI‑first; DivKit (Yandex / community): iOS (UIKit/Swift), Android, Web
  • Layout model — Uzori iOS SDK (SwiftUI): Typed SwiftUI div schema, oriented to conversational/assistant flows; DivKit (Yandex / community): JSON div tree with flexible widgets and layout engines
  • AI integration — Uzori iOS SDK (SwiftUI): First‑class; Uzori engine consumes LLM output + OpenAPI; DivKit (Yandex / community): Not AI‑aware by default; you can bolt LLMs on yourself
  • Streaming — Uzori iOS SDK (SwiftUI): Yes, designed to stream AI‑generated SwiftUI screens; DivKit (Yandex / community): Typically full‑page JSON fetch; streaming possible but not core
  • Validation model — Uzori iOS SDK (SwiftUI): Server‑side schema + business rule validation, then stream; DivKit (Yandex / community): Schema‑like config; validation often left to client/business logic
  • Supported primitives — Uzori iOS SDK (SwiftUI): Stacks, cards, media, actions, lists, forms, carousels tailored for assistants; DivKit (Yandex / community): Rich widget set for generic app UIs (texts, images, grids, etc.)
  • Performance — Uzori iOS SDK (SwiftUI): Optimized for lightweight, single‑screen conversations; SwiftUI diffing; DivKit (Yandex / community): Mature, performant, used in large‑scale Yandex apps
  • Licensing — Uzori iOS SDK (SwiftUI): Commercial / SaaS (Uzori platform); DivKit (Yandex / community): Open source (Apache‑style license; check project for details)
  • Best use cases — Uzori iOS SDK (SwiftUI): AI concierges, rebooking/refund flows, dynamic explorers; DivKit (Yandex / community): Static SDUI screens, marketing/configurable layouts

When to use which:

  • Choose DivKit if you want a general SDUI engine and you’ll handle AI integration yourself.
  • Choose Uzori if your primary need is AI‑driven, server‑validated SwiftUI disruption flows with minimal integration work.

AI UI tools stability & quality considerations

Regardless of framework:

  • Anchor your layouts to typed div schemas.
  • Enforce server‑side validation before UI hits the device.
  • Monitor latency and error rates when streaming AI‑generated layouts.

This is the difference between an experiment and a production‑grade AI interface layer beyond chat UI.

Server‑side validation for safe AI‑generated layouts

For disruption flows, server‑side validation is non‑negotiable. The DOT explicitly sought to end inconsistent refund handling by clarifying rules and requiring automatic refunds in 2024 (DOT Final Rule, 2024, accessed 2026‑09‑04). Your UI must reflect that reliably.

Here’s what to validate before streaming a div tree to iOS:

1. Allowed block types

  • Reject unknown type values.
  • Maintain an allowlist: screen, stack, card, media, text, action, list, form.
  • Version gate: only allow new block types once clients support them.

2. Size and complexity limits

  • Maximum depth (e.g., 8 levels) to prevent recursion.
  • Maximum children per node and nodes per screen.
  • Enforce payload size caps (e.g., 100 KB per screen).

3. Media whitelisting

  • Restrict media.url to your trusted domains (^https://media.example.com/).
  • Optionally add an asset registry: media.assetId must exist in your catalog.
  • Validate aspectRatio ranges to avoid layout breakage.

4. Action allowlists

  • Constrain action.kind to a fixed enum: rebook, refund, supportChat, call.
  • Validate action.target against known routes or identifiers.
  • Enforce parameter constraints (e.g., optionId must be a known offer id).

5. User‑data substitution rules

  • Allow divs to reference dataKey (e.g., booking.departureTime).
  • Perform substitution server‑side; never let LLMs see raw PII.
  • Validate that substituted strings meet length and formatting limits.

6. Recommended CI test harness

  • Maintain schema fixtures for key flows: disruption summary, rebooking, refunds.
  • Add a CI job that:
    • Generates screens from representative LLM prompts.
    • Runs them through your validation pipeline.
    • Fails builds on schema violations or unsafe actions.
  • Include regression tests for regulatory scenarios (e.g., DOT significant change vs. not).

This ensures your AI UI solutions don’t break existing flows when you evolve schemas.

Non‑chat AI interface design patterns for disruption

AI‑native disruption UX should feel like the rest of your app, not like an embedded chatbot.

Pattern 1: Conversational wizard → adaptive layout

  • The user explains constraints (“I must arrive tonight”, “I’m traveling with kids”).
  • LLM interprets constraints and composes a div tree:
    • Step 1: cards summarizing constraints.
    • Step 2: rebooking options filtered accordingly.
  • The SwiftUI screen updates as the conversation progresses.

Pattern 2: Comparison view for alternative flights

  • Use stack + card to show two or three options side‑by‑side.
  • Each card includes:
    • Time delta vs. original.
    • Overnight indicator.
    • Refund eligibility if declined.
  • LLM can dynamically add or remove cards, but the layout shell is fixed.

Pattern 3: Post‑decision confirmation & receipts

  • After a rebooking or refund, show a card summarizing:
    • New itinerary or refund amount.
    • Regulatory basis (e.g., DOT automatic refund, EU rerouting rule) in text.
  • Provide a media block for boarding pass or receipt QR.

These patterns generalize to other domains (e.g., commerce returns, subscription changes) but disruption is a particularly demanding testbed.

Ethical AI iOS SDK privacy best practices

Building a secure AI app architecture for user data privacy is critical, especially in regulated travel.

Minimal data exposure

  • Keep PII and booking data server‑side.
  • Provide LLMs with abstracted context:
    • booking.ageInHours, not exact timestamps.
    • ticketType: "refundable" | "nonRefundable" instead of full fare text.
  • Strip names, contact info, and payment data from AI prompts.

Server‑side validation and masking

  • Run all LLM output through:
    • Schema validation (as above).
    • PII scanners to catch leaked names/emails.
  • Reject or redact any output that attempts to echo sensitive data.

Local vs remote inference

  • For sensitive flows, prefer server‑hosted LLMs under your control.
  • If using third‑party APIs, ensure:
    • Clear data processing agreements.
    • No retention or training on your prompts/results.

Logging and PII handling

  • Avoid logging full prompts or div trees when they contain identifiers.
  • Use event logging with hashed or pseudonymous IDs.
  • Limit access to debug screenshots and ensure they’re short‑lived.

These practices align with building an ethical AI iOS SDK experience that respects user privacy while still enabling rich, AI‑driven disruption flows.

How Uzori fits your SwiftUI stack

Uzori is designed to be an AI interface layer for SwiftUI:

  • Integrates as a single SwiftUI view in your existing navigation.
  • Connects to your backend via OpenAPI‑described APIs.
  • Uses a typed div schema similar to the one above.
  • Streams server‑validated SwiftUI screens into your app.

This means you can:

  • Launch an AI concierge for disruption as a feature‑flagged screen.
  • Iterate on layouts, copy, and flows without shipping new binaries.
  • Maintain native performance and polish, because everything is SwiftUI.

FAQ: Building AI‑driven disruption flows in SwiftUI

How does validation work with Uzori or similar AI UI layers?

  • The LLM proposes a div tree.
  • Your backend applies JSON Schema validation and business rules.
  • Only validated trees are streamed to the iOS client.
  • The client does not execute arbitrary logic — it only renders allowed blocks.

What happens if the div tree is invalid or the AI fails?

  • Implement a fallback UI path:
    • Show a standard disruption screen using your existing SDUI templates.
    • Optionally route to customer support.
  • Uzori‑style architectures make this easy: the host screen can choose between AI‑generated and static layouts based on server response.

How do we handle offline or flaky connectivity?

  • Cache the last known disruption screen locally as JSON.
  • Provide a minimal offline view with:
    • Current status snapshot.
    • Call center numbers.
  • Degrade gracefully: disable rebook/refund actions that require fresh inventory.

How do we version schemas without breaking old clients?

  • Include a version field in your screen root.
  • Use content negotiation:
    • Client sends supported schema versions.
    • Server responds with compatible layouts.
  • Maintain migration tests in CI to ensure new schema changes don’t break older apps.

Can we reuse this pattern beyond flight disruption?

Yes. The same div‑based server‑driven SwiftUI stack works for:

  • Commerce returns and exchanges.
  • Subscription upgrades/downgrades.
  • Complex onboarding wizards.
  • Any flow where users ask “What are my options now?” and AI can curate choices into native UI.

Conclusion: Let AI design the disruption answer, not just explain it

Disruption is a mainstream flow, not a corner case. With on‑time performance at 78.1% and a meaningful cancellation rate of 1.4% in 2024 (BTS 2024, accessed 2026‑09‑04), your rebooking and refund experience is as important as your booking funnel.

By combining:

  • Div‑based, server‑driven layouts,
  • SwiftUI rendering, and
  • LLM‑driven orchestration within a validated schema,

you can turn “What are my options now?” into a single, clear, native screen — not a stressful hunt through policy pages or chat transcripts.

Uzori’s iOS SDK is one way to get there quickly: one screen to integrate, infinite AI‑powered disruption flows to explore.

← All posts