How to Encode div Data Contracts for AI‑Generated Media in Your iOS Backend

By the end of this tutorial, you'll have a concrete, JSON-based div data contract for images, video, and text that:

Angled beam of light entering through a narrow gap, breaking into structured lines across a floor

By the end of this tutorial, you'll have a concrete, JSON-based div data contract for images, video, and text that:

  • Your backend owns and validates
  • An LLM can safely populate
  • Uzori can translate into native SwiftUI screens and stream into your iOS app

You'll treat AI UI like an API contract, not a prompt: every image, div video, and div text block lives inside a typed schema that your server checks before it reaches users.

This tutorial focuses on contracts and validation. For a deeper dive into compositional media layouts, see this related guide: Divs for Media Layout on iOS: A Pillar Guide to Image, Video and Text Overlays.

Prerequisites

To follow this step-by-step guide, you should have:

  • A backend service (Node, Python, Go, etc.) where you can define JSON schemas
  • A native iOS app using SwiftUI
  • Basic familiarity with server-driven UI or DivKit-style JSON layouts
  • Access to an LLM or AI orchestration layer that you control
  • Access to Uzori's iOS SDK and server-side validation layer (or a similar contract validator)

We'll use the term div data in a DivKit-inspired sense: a JSON object describing layout and media elements (div-image, div-video, div-text), not an HTML <div>.

Step 1 – Model your div data contract as a strict JSON schema

The most important step is to define a single source of truth for media divs: a JSON schema (or similar) that your AI must obey and your backend validates.

1.1 Decide on a minimal media contract

Start with three core element types, aligned with DivKit's primitives:

  • div-text
  • div-image
  • div-video

Each element should have:

  • A mandatory type field
  • Common layout fields (position, size)
  • Type-specific fields (e.g., text, url, sources)

Example high-level schema (pseudo‑JSON Schema):

{
"oneOf": [
{ "$ref": "#/definitions/DivText" },
{ "$ref": "#/definitions/DivImage" },
{ "$ref": "#/definitions/DivVideo" }
],
"definitions": {
"Layout": {
"type": "object",
"properties": {
"x": { "type": "number", "minimum": 0, "maximum": 1 },
"y": { "type": "number", "minimum": 0, "maximum": 1 },
"width": { "type": "number", "minimum": 0, "maximum": 1 },
"height": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["width", "height"]
},
"DivText": {
"type": "object",
"properties": {
"type": { "enum": ["div-text"] },
"id": { "type": "string" },
"layout": { "$ref": "#/definitions/Layout" },
"text": { "type": "string", "maxLength": 500 }
},
"required": ["type", "id", "layout", "text"]
},
"DivImage": {
"type": "object",
"properties": {
"type": { "enum": ["div-image"] },
"id": { "type": "string" },
"layout": { "$ref": "#/definitions/Layout" },
"url": { "type": "string", "format": "uri" },
"contentMode": { "enum": ["fill", "fit"] }
},
"required": ["type", "id", "layout", "url"]
},
"DivVideo": {
"type": "object",
"properties": {
"type": { "enum": ["div-video"] },
"id": { "type": "string" },
"layout": { "$ref": "#/definitions/Layout" },
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": { "type": "string", "format": "uri" },
"mimeType": { "type": "string" }
},
"required": ["url"]
},
"minItems": 1
},
"autoplay": { "type": "boolean" }
},
"required": ["type", "id", "layout", "sources"]
}
}
}
``

### 1.2 Common failure at this step

**Failure:** Leaving fields optional “for now” (e.g., optional `url` on `div-image`). LLMs will happily omit them, and your client will crash or show blank UI.

**Fix:** Make all **render-critical fields required** in the schema and enforce them server-side.

---

## Step 2 – Encode positional and size constraints the AI must respect

AI will try to use all available degrees of freedom. Your contract must **narrow the space** so layouts are valid and readable.

DivKit itself validates min/max constraints and logs when invalid values appear. You want similar behavior.

### 2.1 Normalize units

Pick a single layout model and stick to it:

- **Normalized fractions** (0.0–1.0) for `x`, `y`, `width`, `height` (recommended)
- Or fixed-point grid units (e.g., columns/rows)

Update your `Layout` definition accordingly (as in Step 1) and document it for both AI and client.

### 2.2 Add layout rules as code, not prose

On the server, add explicit rules such as:

- `width + x <= 1.0`
- `height + y <= 1.0`
- `width >= 0.2` for text overlays, so they remain legible
- For `div video`, enforce a minimum height to avoid unusable thumbnails

Example (TypeScript-style pseudo-code for validation):

```ts
function validateLayout(layout: Layout): string[] {
const errors: string[] = [];
if (layout.x < 0 || layout.x > 1) errors.push("x must be between 0 and 1");
if (layout.y < 0 || layout.y > 1) errors.push("y must be between 0 and 1");
if (layout.width <= 0 || layout.width > 1) errors.push("width must be in (0,1]");
if (layout.height <= 0 || layout.height > 1) errors.push("height must be in (0,1]");
if (layout.x + layout.width > 1) errors.push("x + width must be <= 1");
if (layout.y + layout.height > 1) errors.push("y + height must be <= 1");
return errors;
}

2.3 Common failure at this step

Failure: Relying on the SwiftUI client to “fix” invalid sizes or positions.

Fix: Treat the layout as contractually correct or reject it. Let your validation layer strip or repair invalid elements before they reach the iOS app.

Step 3 – Define safe div-image contracts for AI-generated media

Images are usually where AI-generated layouts start: product cards, hero banners, etc. Your div-image contract must make image URLs, aspect ratios, and fit modes explicit and safe.

DivKit’s div-image requires source settings and documents validation errors when they’re missing. You should mirror that rigor.

3.1 Extend the image contract with media-specific rules

Add fields that matter for your app:

{
"type": "div-image",
"id": "hero",
"layout": { "x": 0, "y": 0, "width": 1, "height": 0.5 },
"url": "https://cdn.example.com/images/hero-1234.jpg",
"contentMode": "fill",
"accessibilityLabel": "Mountain landscape with product overlay",
"maxFileSizeKB": 512
}

On the server, enforce:

  • url matches your CDN (^https://cdn\.example\.com/…)
  • Optional max pixels or file size (if you proxy the image)
  • contentMode constrained to behaviors you map cleanly to SwiftUI (.fill, .fit)

3.2 Validate image URLs and constraints in Uzori’s server layer

With Uzori, you register a typed schema and let the server validation layer check each div-image before streaming SwiftUI:

  • Reject images with missing url
  • Reject disallowed domains
  • Downgrade or remove images that don’t meet layout constraints (e.g., too small)

Common pattern:

  • If validation fails, either:
    • Remove the element and continue, or
    • Return a structured error to the AI and let it retry

3.3 Common failure at this step

Failure: Allowing any arbitrary image URL, including user-submitted or third-party domains.

Fix: Enforce a strict image URL allowlist and, where possible, serve media via your own proxy.

Step 4 – Define div-text overlays as first-class, constrained elements

Text overlays (div text on image) are powerful — but easy to break with long, unbounded copy. Your schema must limit length and placement.

DivKit’s div-text docs specify required fields and max lengths; use that as inspiration.

4.1 Model a robust div-text block

Example:

{
"type": "div-text",
"id": "headline",
"layout": { "x": 0.05, "y": 0.6, "width": 0.9, "height": 0.2 },
"text": "Find the perfect roaming plan in seconds.",
"style": {
"role": "headline",
"maxLines": 2
}
}

Server-side rules:

  • text must be non-empty
  • text length ≤ e.g. 120 characters for headlines
  • maxLines between 1 and 4
  • layout.height large enough for the chosen role

4.2 Enforce textual safety and brand tone (optional but recommended)

Because div-text is where language appears, you can add extra checks:

  • Profanity or policy filters
  • Brand tone classifiers (e.g., no ALL‑CAPS headlines)
  • Limited punctuation or emojis if needed

4.3 Common failure at this step

Failure: Letting the AI output unbounded text in a small overlay.

Fix: Hard-code max lengths and maxLines, and truncate or reject on the server. Never rely on Text in SwiftUI to “just wrap it somewhere”.

Step 5 – Define div-video contracts with explicit source requirements

Video is where serious validation pays off. DivKit’s div-video element explicitly errors when required source settings are missing. Your backend must do the same.

5.1 Model the video element with required sources

Example contract:

{
"type": "div-video",
"id": "promo-video",
"layout": { "x": 0, "y": 0, "width": 1, "height": 0.4 },
"sources": [
{
"url": "https://cdn.example.com/video/promo-720p.mp4",
"mimeType": "video/mp4"
}
],
"autoplay": false,
"showControls": true
}

Validation rules:

  • sources non-empty
  • Each url passes the same CDN allowlist
  • mimeType in {"video/mp4", "video/hls"} (or your supported set)
  • layout.height above a minimum (e.g., 0.25)

5.2 Map div-video to SwiftUI players safely

In Uzori, your server-validated div-video translates to a SwiftUI view such as:

VideoPlayer(player: AVPlayer(url: sourceURL))
.frame(maxWidth: .infinity)
.aspectRatio(16/9, contentMode: .fit)

Because every div-video was checked on the server, the iOS client can:

  • Assume there is at least one valid URL
  • Use a known set of MIME types
  • Trust that layout constraints are already satisfied

5.3 Common failure at this step

Failure: Treating video as “nice to have” and falling back silently when sources are invalid.

Fix: Make video requirements explicit in the schema and validation. If they’re not met, fail fast or provide a deterministic fallback (e.g., static thumbnail + CTA).

Step 6 – Wire the contract into Uzori’s server validation and SwiftUI streaming

Now that you have a div data contract for div images, div video, and div text, you need to plug it into Uzori’s architecture so the pipeline becomes:

LLM → div data JSON → Uzori server validation → SwiftUI screens streamed into your app

Uzori positions this as “Generative UI, server-driven safety.” You get AI‑composed layouts but only within your contract.

6.1 Register your schema with Uzori’s validation layer

At a high level, you:

  1. Define your div schema in code (e.g., using JSON Schema, TypeScript types, or a Rust/Go struct model).
  2. Configure Uzori’s backend to accept only those shapes from the AI.
  3. Implement validators that:
    • Check layout bounds
    • Enforce URL allowlists
    • Enforce size and text length constraints

Any element that fails validation is either stripped from the payload or triggers a retry.

6.2 Stream SwiftUI screens in the iOS app via Uzori SDK

On the iOS side, you integrate the Uzori SDK as a single SwiftUI screen:

struct AssistantView: View {
var body: some View {
UzoriScreen(sessionConfig: .init(assistantId: "media-concierge"))
}
}

When the user interacts with the assistant:

  1. The app sends user intent + context to your backend.
  2. Backend calls the LLM, asking it to produce div data conforming to your schema.
  3. Uzori validates the returned JSON against the contract.
  4. Valid divs are translated into SwiftUI (Image, Text, VideoPlayer, stacks, etc.) and streamed back into the running app.

6.3 Common failure at this step

Failure: Letting the AI return arbitrary JSON and trying to “massage” it into the contract.

Fix: Make the schema the only accepted format and configure the AI tools (or function calling) to emit exactly that shape.

Step 7 – Add contract-level tests and guardrails for AI regressions

Once your div data contract is live, treat it like any other API:

  • Version it
  • Test it
  • Monitor violations

7.1 Write contract tests for typical AI outputs

For each key flow, create fixtures the AI is expected to produce, such as:

  • A hero banner: div-image + div-text overlay
  • A product card grid: repeating div-image + div-text
  • A media detail page: div-video + description text

Run them through your validation library in CI. Fail the build if any element:

  • Breaks the schema
  • Violates layout constraints
  • Uses disallowed URLs or MIME types

7.2 Monitor validation errors in production

Log validation failures with:

  • Element type (div-image, div-video, div-text)
  • Reason (e.g., url_disallowed, layout_out_of_bounds, text_too_long)
  • AI prompt / tool invocation that produced them

Over time, you’ll:

  • Tighten schema where needed
  • Improve prompts or tool specs for your LLM
  • Gain confidence that AI‑generated UI is stable and safe

7.3 Common failure at this step

Failure: Treating validation errors as noise instead of product signals.

Fix: Wire errors into your observability stack and treat them as first-class incidents for the AI UI pipeline.

FAQ: Troubleshooting div data contracts for AI-generated media

1. How strict should my div data contract be for AI-generated UI?

Start stricter than you think. Require all render-critical fields (url, text, sources, layout). You can always relax constraints later, but shipping with a loose contract means unstable UI from day one.

2. How do I stop the AI from overlapping divs in unreadable ways?

Add explicit rules to your validation layer:

  • Reject overlapping layouts for certain roles (e.g., overlapping div-text blocks)
  • Enforce minimum spacing (x/y gaps)
  • Restrict how many elements of a given type can appear per screen

If validation fails, either repair (e.g., stack text vertically) or return an error and let the AI retry with guidance.

3. Can I reuse my DivKit contracts directly with Uzori?

Conceptually, yes. DivKit’s JSON model for div-image, div-video, and div-text is a strong reference. In practice, you’ll likely:

  • Map DivKit fields to your own contract
  • Align layout semantics with your SwiftUI needs
  • Plug that contract into Uzori’s validation and SwiftUI rendering layer

The key idea is the same: schema-first, server-validated UI.

4. What happens if the AI omits a required field like url on div-image?

If you implemented the schema and Uzori validation correctly, the element will be rejected before it reaches the client. You can:

  • Drop that element and render the rest of the layout, or
  • Treat it as a hard error and ask the AI to generate a new layout

Never let an element with missing critical fields reach SwiftUI.

5. Is SwiftUI fast enough for server-driven, AI-generated interfaces?

Yes. Apple positions SwiftUI as the best choice for new apps and the default for watchOS. Its declarative model (“write the result, not the instructions”) lines up well with server-driven UI and AI-generated layouts. Uzori sits on top of that, streaming validated SwiftUI views so your AI UI feels just as native and performant as the rest of your app.

By encoding your div data contracts this way — strict schema, server validation, and SwiftUI streaming via Uzori — you turn LLM responses into reliable, shippable native interfaces rather than brittle chat transcripts. That’s the difference between “an AI in your app” and an AI that is your app’s interface.

← All posts