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:

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-textdiv-imagediv-video
Each element should have:
- A mandatory
typefield - 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:
urlmatches your CDN (^https://cdn\.example\.com/…)- Optional max pixels or file size (if you proxy the image)
contentModeconstrained 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:
textmust be non-emptytextlength ≤ e.g. 120 characters for headlinesmaxLinesbetween 1 and 4layout.heightlarge enough for the chosenrole
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:
sourcesnon-empty- Each
urlpasses the same CDN allowlist mimeTypein{"video/mp4", "video/hls"}(or your supported set)layout.heightabove 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:
- Define your div schema in code (e.g., using JSON Schema, TypeScript types, or a Rust/Go struct model).
- Configure Uzori’s backend to accept only those shapes from the AI.
- 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:
- The app sends user intent + context to your backend.
- Backend calls the LLM, asking it to produce
div dataconforming to your schema. - Uzori validates the returned JSON against the contract.
- 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-textoverlay - 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-textblocks) - Enforce minimum spacing (
x/ygaps) - 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.