Pattern · Prompt Input

New

Model Composer SwiftUI Block

A message composer with a model menu, Search, Think and Image chips, and a usage line that warns near the limit.

A composer pinned above the keyboard with a model pill whose menu describes each model and marks locked ones as Pro, mode chips that toggle, and a line of messages left that turns to the warning colour near the limit. At the limit the composer turns off and offers Upgrade; the app owns the model and modes through bindings.

  • SwiftUI
  • iOS 17.0+
  • composer-modes
Get SwiftPieces Pro

$199 once, every piece included. See how installing works · Sign in

01

What's included

SwiftUI source you own, built on Apple frameworks only.

  • A model menu with a line about each model and Pro on locked ones
  • Search, Think and Image chips that show their name when on
  • "12 messages left today", warning near the limit
  • Turns off at the limit with Upgrade
  • Bindings for model and modes, and Send that turns into Stop

02

Implementation notes

How it behaves, what it handles, and the decisions behind it.

The composer is one card pinned above the keyboard: a field that grows from one line to six, then a row with the model pill, the mode chips and Send. The pill names the current model; its menu lists every model with a line about it, a check on the current one and a lock with "Pro" on the ones the plan doesn't include, which call your closure instead of switching. Mode chips are hairline rings around a symbol while off and fill with their name when on, and any number can be on. Under the card a line reads "12 messages left today"; from five left it turns to the warning colour with a symbol, and at the limit the field and controls turn off, the prompt reads "You've reached today's limit" and the line says when more arrive, with Upgrade at its end. While your onSend runs Stop takes Send's place, and a failed send hands the text back with the error under the card.

Notes

  • ModelComposer(model:modes:models:availableModes:usage:placeholder:onLocked:onUpgrade:onSend:). It is a section: no navigation stack, background, padding or scroll view of its own. Pin it with .safeAreaInset(edge: .bottom) and .padding(.horizontal, 20) on a theme.color.bg background.
  • model is a Binding<ModelComposer.Model.ID> and modes a Binding<Set<ModelComposer.Mode.ID>>, so your app owns both and can keep them per conversation. An id that isn't in models falls back to the first unlocked model.
  • ModelComposer.Model is an id, name, one-line detail and isLocked. Model.samples is Atlas 3 Mini, Atlas 3 and Atlas 3 Max, the last locked. Picking a locked model leaves the binding alone and calls onLocked with it; show your upgrade there.
  • ModelComposer.Mode is an id, title and SF Symbol. .search, .think and .image are ready and Mode.defaults lists them. Pass an empty availableModes to hide the chips.
  • ModelComposer.Usage is remaining, limit, an optional resets date and warnAt (5 by default), with isLow and isExhausted. The line reads "12 messages left today", or "12 messages left until 9:00 AM" with a reset time, and "You've used all 40 messages. More at 9:00 AM." at the limit. The block never counts: hand it a new Usage after each message. Nil hides the line and never limits.
  • onUpgrade shows Upgrade at the end of the line once the limit is reached. Nil hides it.
  • onSend receives ModelComposer.Message: the text, trimmed, the Model and the Modes that were on, in chip order. Stop cancels the task and frees the composer at once. Any other thrown error shows its errorDescription, or "Couldn't send. Try again.", and puts the text back.
  • At accessibility text sizes the chips keep to their symbols so the row stays on one line; the pill and chips keep 44 point targets.
  • Haptics: a selection tick when the model or a mode changes, a light impact on Send, an error when a send fails.
  • VoiceOver reads the pill as "Model" with the current name, each chip by name with On or Off and the selected trait, and the usage line as a sentence. Model changes, "Stopped" and failures are announced.
  • Reduce Motion turns the chips' change and the count's roll into short fades.
  • ModelComposerExample at the end of ModelComposer.swift is the Research thread from the preview: Atlas 3 with 12 of 40 messages left, counting down with each message, and an upgrade prompt from the locked model or from Upgrade. Pass wrapsInNavigationStack: false to push it in your own stack.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • The card is color.surface with radius.lg and a 1 point separator hairline that turns to textPrimary while the field is focused. The field is type.body in textPrimary, its prompt textTertiary.
  • The model pill is a fill capsule with its name in type.subhead semibold textPrimary and the chevrons in textSecondary. Chips are 32 point capsules: off, a separator ring around a textSecondary symbol; on, fill with the filled symbol and the name in type.subhead medium textPrimary.
  • Send is the accent: an accent circle with an accentOn arrow, fill with textTertiary while off. At the limit Send is off and Upgrade, in type.subhead semibold accent, is the one accent left.
  • The usage line is type.footnote with monospaced digits in textSecondary, turning to warning near and at the limit. Errors are type.footnote in danger.
  • State changes use motion.smooth and motion.snappy, and become short fades under Reduce Motion.