Pattern · Prompt Input
NewDictation Composer SwiftUI Block
A message composer with voice input: a live level meter, elapsed time, Cancel and Done, and the transcript filling the field.
A composer pinned above the keyboard whose mic turns the bar into a recording state, with a level meter that moves with the voice, the elapsed time, Cancel and Done, while the transcript fills the field. It runs on the Speech framework and AVAudioEngine, asks for access and offers Settings when it is off, and takes your own transcription for previews or another engine.
- SwiftUI
- iOS 17.0+
- composer-dictation
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- The mic turns the bar into Cancel, a live level meter, the time and Done
- The transcript fills the field as you speak, after anything typed
- Speech framework and AVAudioEngine, with access asked and Open Settings when off
- Your own transcription through one value, or a scripted one for previews
- Send turns into Stop while the reply runs
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, with the mic and Send on the row under it. Tapping the mic asks for speech recognition and the microphone the first time, then turns the row into a recording state: Cancel on the left, a red dot with the elapsed time and a level meter of thin bars that rise and fall with your voice, and Done in the accent on the right, while the card's hairline turns to ink and the field fills with the transcript as it is heard. Words land after anything already typed. Done keeps them, Cancel puts the field back as it was, and dictation finishes on its own at one minute. When speech recognition or the microphone is off, a line under the card says so with Open Settings; when dictation is restricted or unavailable it says that instead. Send stays off until there is text; while your onSend runs, Stop takes its place and cancels the task, and a failed send hands the text back with the error under the card.
Notes
DictationComposer(text:placeholder:transcription:maxDuration:onSend:). OnlyonSendis required. 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 atheme.color.bgbackground.onSendreceives the text, trimmed. Stop cancels the task and frees the composer at once; any other thrown error shows itserrorDescription, or "Couldn't send. Try again.", and puts the text back.DictationComposer.Transcriptionis the engine behind the mic:access(whether it may run now),requestAccess(asks, returning the result) andstart(anAsyncThrowingStreamofUpdates, each with the whole transcript so far, a level from 0 to 1, or both). The block cancels the stream on Done and Cancel; the stream may also end by itself, which counts as Done..speech(locale:), the default, runsSFSpeechRecognizerwith partial results and punctuation on microphone audio from anAVAudioEnginetap, measuring each buffer's loudness for the meter. It asks for speech recognition, then the microphone, and stops the engine and releases the audio session when the stream ends..scripted(_:wordsPerSecond:)plays a fixed transcript with a moving level, for previews, tests and the simulator;Transcription.sampleis one.DictationComposer.Accessis.allowed,.notDetermined,.denied,.restrictedor.unavailable. Denied shows "Turn on Microphone and Speech Recognition in Settings to dictate." with Open Settings, which opens your app's page in Settings.DictationComposer.TranscriptionErroris.unavailable("Dictation isn't available right now.") or.interrupted("Dictation stopped. Try again."); the default engine reports recognizer failures as.interrupted. A failure keeps what was heard so far in the field.maxDurationis one minute by default, the Speech framework's own limit for one request.textis aBinding<String>for a draft your app keeps.- Haptics: a start tap when recording begins, success on Done, a light impact on Send, an error on a refusal or failure.
- VoiceOver reads the field with the transcript as its value ("Listening" until words come), the recording row as "Recording" with the time, and Cancel dictation and Done with hints. Refusals, "Dictation added", "Stopped" and failures are announced. Every control has a 44 point target.
- Reduce Motion keeps the meter still between readings and turns state changes into short fades.
DictationComposerExampleat the end ofDictationComposer.swiftis the Kitchen thread from the preview. On a device the mic uses the Speech framework; in the simulator and in Xcode previews it plays a scripted transcript. Sending holds the reply for two seconds so Stop can be tried. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - The card is
color.surfacewithradius.lgand a 1 pointseparatorhairline that turns totextPrimarywhile the field is focused or recording. The field istype.bodyintextPrimary, its prompttextTertiary. - The mic and Cancel are 36 point
fillcircles withtextPrimarysymbols in 44 point targets. Send and Done are the accent:accentcircles withaccentOnsymbols, Send onfillwithtextTertiarywhile off. Only one of them shows at a time. - The meter's bars are
textPrimary; the recording dot isdanger, a real status. The time istype.subheadwith monospaced digits intextSecondary. - The access line is
type.footnoteintextSecondarywith Open Settings intype.subheadsemiboldtextPrimary; errors aretype.footnoteindanger. - State changes use
motion.smoothandmotion.snappy, and become short fades under Reduce Motion.
Requirements
NSMicrophoneUsageDescriptionandNSSpeechRecognitionUsageDescriptionin Info.plist, for the default.speech()transcription.
03
Keep building
Pieces that work well next to this one.