Docs
AI

Streaming Reply

The AI reply in liquid glass: text that streams in word by word under a morphing phase label, with copy, regenerate and retry.

$npx swiftpieces add StreamingReply
Free · MIT + Commons ClauseiOS 17.0+Liquid Glasschatstreamingmarkdownaitext-rendererblocks
Type
StreamingReply
Files
StreamingReply.swift
Depends on
Nothing (Apple frameworks only)
Version
2.2.0
In a full screen · SwiftPieces ProResearchA research answer that writes itself word by word, with sources and a follow-up bar.

A chat message that streams. Assistant replies sit on the ground under a glass header pill; user messages are a signal-tinted glass bubble.

Notes

  • Assistant replies sit on the ground under a liquid glass header pill: a 24pt ink mark, the name (style.assistantName) and a small uppercase phase label (THINKING, WRITING, STOPPED) that morphs letter by letter, the letters two phases share holding still, while the pill reshapes around it. User prompts are signal-tinted glass bubbles (style.userBlock) with dark house ink and a tucked bottom-trailing corner. Set style.showsHeader = false for a bare reply.
  • Text is split into word tokens (each word keeps its trailing whitespace), and each new word settles like ink over fadeDuration: its opacity eases out, so it reads almost at once, and on iOS 18 a 2pt rise trails it and comes to rest just after the word is fully inked. On iOS 18 a private TextRenderer draws each token this way; on iOS 17 the same opacities are written into the AttributedString runs, without the rise. Words that arrive together in one update ink in left to right, 24ms apart, never more than 0.12s (or 40% of fadeDuration) behind the stream. Only words that arrive after the view appears animate, so loaded history renders instantly.
  • The reply text is not selectable: selectable text is drawn without the renderer, so words would land without their ink and the cursor would never breathe. Copy in the lifted actions takes the whole message.
  • Inline markdown renders in one weight: every string is semibold, strong emphasis included. Inline code sits on a small rounded chip, a faint wash of the text colour, so it reads on the ground and on a tinted bubble alike; set style.code to tint it, with dark ink on top. On iOS 17 the chip is the run's background.
  • The cursor is a small tinted bubble sized from the body font's cap height, so it flows with the text and scales with Dynamic Type. On iOS 18 it holds solid while words land, like a caret while typing, and breathes slowly once the stream pauses; on iOS 17 it is solid. Streaming with no words yet shows the cursor alone on the line the thinking dots used, so the reply never collapses and regrows.
  • phase is a value, not internal state: drive .thinking (block-colored dots rising in sequence, each easing out of rest and back, and a pulsing mark), .streaming, .done, .error(message) from your stream. New phases come in calmly and .done clears the label and cursor firmly. Entering .error turns the mark signal, shakes it once by 3pt with an error haptic, shows the message under the reply and buds a signal Retry out of the end of the header pill, joined to it by a liquid neck; it calls onAction(.regenerate) and melts back into the pill when the phase moves on. Without a header, Retry sits beside the message on its own glass.
  • Press and hold: after a short beat the message sinks a touch toward its tail, without bounce, and springs back if you let go early; at 0.35s it springs up with a rigid impact as the header pill swells into a glass card around the whole reply, and Copy and Regenerate bud out of the card's bottom edge, hanging a neck's width under it and resting apart from each other. A user's bubble lifts the same way and buds Copy alone. The room they take opens on a calm spring with only a slight give, so the messages around it move without bobbing. Copy writes to UIPasteboard, its glass tints sage, its glyph swaps to a check and its label morphs to Copied, and it pops with a success haptic; the message is set down 0.9s later unless you picked it up again. Tap the message to put it down: it lands with a little give while the actions melt back and the card shrinks into the pill, with no bounce. The lift's shadow is cast from the card's outline. Copy, Regenerate and Retry sink under the finger, glass and label together, and spring back.
  • Style (StreamingReply.Style): userBlock, cursor, code, dots, signal, ink, text, muted, mark, markInk, actionInk, success, assistantName, showsHeader. surface and actionFill are kept for source compatibility and no longer used. tint: still overrides the user bubble and cursor.
  • Under Reduce Motion new words still fade in, together and without the rise. The cursor holds solid, the mark neither pulses nor shakes (its signal color shows the error), and the thinking dots stay in place while a ripple runs through their opacity, so thinking still shows a sign of life. The message neither sinks nor scales: the card swells and the shadow fades in for the lift, the bubbles fade in place, labels crossfade, and pressed buttons shade instead of sinking. Reduce Transparency makes every glass shape solid.
  • Need full incremental markdown with lists, tables, source cards and a follow-up bar? That is the Streaming Markdown screen in SwiftPieces Pro.

Usage

StreamingReplyExample().preferredColorScheme(.light)

Parameters

ParameterDescription
textThe message body. Append to it as tokens arrive; each new word fades in. Inline markdown is rendered.
role.assistant (leading, header and open text) or .user (trailing, tinted glass bubble).
phase.thinking shows dots, .streaming shows the cursor, .done settles, .error shows the message and buds a Retry out of the header pill.
tintGlass tint of user messages and the streaming cursor. nil uses style.userBlock and style.cursor.
fadeDurationSeconds each new word takes to fade in.
styleColors, the assistant name and whether the header shows. Defaults to the house palette.
onActionCalled with .copy or .regenerate from the long-press actions or the error retry.

Install

Pick one. Run it from the folder that contains your .xcodeproj and the files land inside your app.

Terminal

$npx swiftpieces add StreamingReply

Or ask your coding agent · Claude Code, Cursor or Xcode, once the MCP server is connected

>Add the SwiftPieces "Streaming Reply" piece to my app

Or copy the source above into your app.

New to SwiftUI?If you know React, CSS or Tailwind, SwiftUI is easier to pick up than it looks.Try real app patterns first

Building a whole app? See SwiftPieces Pro →

All ai →

Build it yourself: SwiftUI loading states guide · SwiftUI animations guide

On this page

SwiftPiecesPro

Build the whole app with one library.

Production-ready screens, whole flows, complete app templates, a Build Kit your agent follows and Pro remixing.

54 screens · 46 patterns · 48 flows · 12 templates · 31 Build Kit skills

Explore Pro