Georgii EmelianovEngineering

Generative UI on iOS: what it actually means when the model draws the screen

Every definition of this term is written for React. The part the web glossaries skip is what a compiled app does when the model emits a record it cannot render.

A scored sheet of heavy paper, half of it folded up into a standing faceted structure and half still lying flat on the surface.

Getting a model to emit a JSON description of a screen takes an afternoon. Getting that screen past your own review process takes considerably longer, and the question that stalls it is always the same one: what does the app draw when the model emits something the app cannot draw?

Generative UI on iOS is the name for this class of system, and right now the name covers three unrelated things. Half the results for the phrase are about using an assistant to write SwiftUI code faster. Most of the rest are written for React, where the answer to "what if the output is wrong" is a component that throws and a page that re-renders. Neither describes what happens inside a compiled app that shipped six weeks ago.

This article defines the term for iOS specifically, separates it from the three things it gets confused with, and then gives you the four questions that actually distinguish one implementation from another — including the one the web-first definitions skip.

The short answer

Generative UI on iOS means a model composes the screen at request time instead of selecting between screens a designer already built. The model emits a structured description of an interface; the app renders that description in native SwiftUI. Implementations differ in how much the model is permitted to author, and in what the app does when the model authors something invalid.

What it is not

Three neighbouring things carry the same name in conversation, and telling them apart is most of the work of evaluating this category.

It is not a chat bubble that renders rich text. Markdown tables, bolded headings and a link at the bottom are still a transcript. The distinction is not visual richness — it is whether the result participates in the app: whether a row taps through to the screen it describes, whether a value respects the user's Dynamic Type setting, whether the thing scrolls the way everything else in your app scrolls. A rendered markdown table does none of that.

It is not server-driven UI, though it ships over the same kind of wire. SDUI and generative UI both send a serialized description of an interface to a client that renders it natively. The difference is authorship: in SDUI a human wrote that description and a deploy shipped it, so it can be reviewed, diffed and rolled back. In generative UI a model composes it per request, which means every property of that description becomes a runtime question — including whether it is well-formed at all. If you already run an SDUI backend you have the transport. You do not yet have the part that matters.

It is not an assistant writing your SwiftUI for you. This is the largest source of confusion on the topic, and it is a difference of when, not of how much. Code generation happens at authoring time: a human reads the result, a compiler checks it, a review approves it, a release ships it. Generative UI happens at request time, on a device, with no human in the loop and no compiler between the model and the user. The same words describe two things with completely different failure modes.

How a generated screen reaches the device

Concretely, for a request-time system rather than a code-generation one.

The request

The app sends the current session state and the user's question. What it does not send is access to itself. The model is given a declared set of operations it may call — in practice a subset of your existing API — and that set is chosen by a human in advance rather than discovered by the model at runtime. Everything the model later says about the user's data has to have come through one of those calls.

The stream

The response arrives as a sequence of records rather than one document, and each one is validated as it lands. Is it a shape the contract declares? Does it belong to the screen currently being built? Does it appear in a position the stream grammar allows? Records that pass are forwarded and drawn immediately, which is why a well-built implementation fills in progressively instead of showing a spinner for the whole turn.

The commit

Content that is still streaming is not yet interactive. A dedicated commit record marks the screen as complete, and only then do controls become tappable. If the turn is abandoned — cancelled by the user, or dropped by the network — the app restores the screen that was there before it started, rather than leaving half an answer on the display.

Uzori is one implementation of this shape. There are others, and the section below is deliberately about the questions rather than about any of them.

The four questions that decide any implementation

The best existing framing of this category is CopilotKit's generative UI spectrum, checked 5 August 2026. It arranges implementations on a single axis. At one end is controlled, where the agent picks from components you registered; at the other is open, where the agent emits raw HTML or SVG and a sandbox is the only thing between it and the user.

That axis is real, and one axis is not enough to evaluate a system you intend to put in front of paying customers. Four questions do the job, and they are worth asking in this order:

  1. Who authors the tree? Does the model choose from record types you declared, or can it invent a container, a nesting depth, or a component you never shipped?
  2. Who authors the style? Can the model set a colour, a font, a spacing value, a corner radius? If the answer is "it can, but we prompt it not to," the answer is that it can.
  3. Who authors the actions? When a generated screen shows a button, where did the destination come from? A label and a URL emitted by the model is a very different object from a reference to an action your app already declared and already knows how to perform.
  4. What happens when a record is invalid? Is it dropped, patched, coerced into the nearest legal shape, or does the whole turn fail? This is the question that gets asked last and decides the most.

Questions two and three are answerable by reading a schema. If the format has a color field, the model can author colour, regardless of what the system prompt asks for. If the format lets an action carry a URL, the model can author navigation. A contract that has nowhere to put a thing is a stronger guarantee than an instruction not to put it there, and it is the one guarantee a reader can verify for themselves in a few minutes.

The useful question is never "can the model be trusted with this?" It is "does the format have a field for it?"

Question four is where implementations diverge most, and where iOS diverges from the web.

Why the fourth question is harder on iOS

Three things the web has here, iOS does not.

You cannot ship code into a compiled app. The open end of that spectrum — the model emitting markup the client executes — requires a runtime that will execute arbitrary markup. On iOS that means embedding a web view, and an app whose assistant is a web view has not built a native interface; it has built a browser with native chrome around it. The continuum the web-first write-ups describe is, on iOS, a cliff with nothing past the edge.

Your component catalog freezes at release, not at deploy. A React team adds a chart component and it is live the same day. An iOS team adds one and it reaches users through review, then gradually, as installs update. Every general treatment of this topic offers "just add a component" as the escape hatch for a case the model wants and the catalog lacks. On iOS that hatch is measured in weeks and never reaches everyone, so the set of record types has to be small, general, and correct from the start.

SwiftUI gives you no DOM to patch. In a browser you can drop a malformed node and keep the rest of the document. A SwiftUI view tree is rebuilt from state, so a partially applied update is not a degraded version of the intended screen — it is a different screen, one nobody designed and nobody reviewed. Silently repairing a bad record does not produce the answer with a small error in it. It produces a plausible-looking screen that is confidently wrong.

A repaired screen is a screen you did not design, shown to a customer as though you had.

Which is why the honest answer to question four is rejection: an invalid record is dropped rather than fixed, and the failure is visible rather than smoothed over. That is the subject of the next several articles on this blog, because it is where the engineering actually is.

What a record actually looks like

Abstractions about contracts are cheap. Here is a real one. This is a record from Uzori's wire format — the shipped schema, not an illustration — carrying the two figures behind a billing answer:

{
  "type": "metrics",
  "heading": "This month vs last",
  "items": [
    { "label": "This month", "value": "$94.20", "tone": "negative" },
    { "label": "Last month", "value": "$61.00", "tone": "neutral" }
  ]
}

And the fragment of the contract that bounds it:

{
  "type": "object",
  "additionalProperties": false,
  "required": ["type", "items"],
  "properties": {
    "type": { "const": "metrics" },
    "items": {
      "type": "array",
      "minItems": 2,
      "maxItems": 4,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["label", "value"],
        "properties": {
          "label": { "type": "string", "minLength": 1, "maxLength": 60 },
          "value": { "type": "string", "minLength": 1, "maxLength": 80 },
          "supportingText": { "type": "string", "minLength": 1, "maxLength": 100 },
          "tone": { "enum": ["neutral", "positive", "negative"] }
        }
      }
    }
  }
}

Four things are checkable by reading it, and they answer three of the four questions above:

  • additionalProperties: false. A field the contract does not name is not ignored — it makes the record invalid. There are 23 record types in this format, and all 26 object definitions in the schema file carry that line.
  • tone is an enum of three semantic values, not a colour. The model states that a number reads as bad news; the app decides what bad news looks like. Search the whole format for a colour, font, spacing or layout field and there is nothing to find, which is why the model cannot author style: there is no field to put it in.
  • maxItems: 4. Cardinality is part of the contract rather than a layout hope. A model that tries to render eleven metrics in a row produces an invalid record, not a squashed one.
  • Actions carry a reference and nothing else. The handoff interaction that ends a screen accepts an actionRef string and nothing besides — no label, no URL, no payload. The model can ask for an action your app already declared. It cannot describe one.

Where this does not fit yet

Four limits worth stating before anyone builds on this.

Read-only is load-bearing, and it is a poor fit for commerce. Generated screens are strongest on questions whose honest answer is a comparison, a set of metrics, or a sequence of steps — bill shock, a delayed flight, a denied claim, a missing order. You cannot rent a dress read-only. If your customers' hardest questions end in a purchase rather than an explanation, this is the wrong shape of tool and the handoff to your existing flow will be doing all the work.

iOS is not mobile. Uzori's implementation is iOS only; there is no Android version. Several of the alternatives are cross-platform first and native second, which is a genuine advantage for them and worth weighing.

The integration inherits your API's accuracy. If the model reaches your service through an OpenAPI spec, the spec has to match what your service actually returns. Drift that a human reader would gloss over becomes a hard failure, and discovering the true state of your specification is often the real first task.

The category is not settled. The phrase has almost no measurable search volume, the protocols are pre-1.0, and anyone describing a standard is describing their own repository. Google's A2UI ecosystem page listed exactly one renderer maintained by the A2UI team when checked on 5 August 2026 — @a2ui/react, for the web. Every native renderer on that list, including the Apple-platform one covering SwiftUI, UIKit and AppKit, is community-maintained. Build against the ideas; hold the specific formats loosely.

Frequently asked questions

Is generative UI the same as server-driven UI?

No. They ship over the same kind of wire — a serialized interface description rendered natively by the client — but a human authors the SDUI payload at deploy time, and a model composes the generative one per request. That difference moves every question about the payload from build time to runtime, which is where the engineering effort goes.

Can an LLM generate SwiftUI code at runtime?

Not in a shipping app. iOS does not execute Swift downloaded after release, so runtime code generation would require an interpreter or a web view, and either choice trades away the native behaviour that motivated a native app. Request-time systems send data rather than code: the model emits a description of a screen, and views already compiled into the app render it.

What happens if the model outputs invalid UI JSON?

That is the design decision, and implementations disagree. The options are to drop the record, repair it into the nearest legal shape, or fail the turn. Uzori rejects: an invalid record is never patched or completed, and one bounded re-ask is permitted only when the very first record fails and nothing has reached the device yet.

Is there a standard for generative UI on iOS?

Not a settled one. A2UI is the most visible attempt and is pre-1.0, with its Apple-platform renderer community-maintained rather than official as of 5 August 2026. MCP Apps and several vendor formats overlap it. Treat the record vocabulary as the durable part and the specific serialization as something you should expect to change.

Where to start

If you are evaluating this category — including an implementation your own team is proposing to build — the four questions are the tool. Who authors the tree, who authors the style, who authors the actions, and what happens to an invalid record. Ask them of a schema rather than of a sales deck; three of the four are answerable by reading a file.

The next articles work through the fourth question in detail. If you would rather see a generated screen answer a real question from your own API first, write to hello@uzori.ai with the question your customers ask most.

← All posts