Transcribe Audio With SpeechAnalyzer In Swift
Apple introduced SpeechAnalyzer as a modern speech-to-text API for iOS 26, iPadOS 26, Mac Catalyst 26, macOS 26, visionOS 26, and tvOS 26. It is unavailable on watchOS in the current SDK. You should still gate real features with SpeechTranscriber.isAvailable and locale support, because model availability is not only a platform-version question.
The simplest useful version is file transcription. Give the API an audio file, collect the results, and return an AttributedString.
The shape of the API
There are two main pieces in this example:
SpeechAnalyzermanages the analysis session.SpeechTranscriberis the module that turns speech into text.
For a recorded file, you can use AVAudioFile and analyzeSequence(from:).
import AVFAudio
import Foundation
import Speech
func transcribeFile(at fileURL: URL, locale: Locale) async throws -> AttributedString {
let audioFile = try AVAudioFile(forReading: fileURL)
let transcriber = SpeechTranscriber(locale: locale, preset: .transcription)
async let transcription = transcriber.results.reduce(AttributedString()) { partial, result in
var partial = partial
partial += result.text
return partial
}
let analyzer = SpeechAnalyzer(modules: [transcriber])
if let lastSample = try await analyzer.analyzeSequence(from: audioFile) {
try await analyzer.finalizeAndFinish(through: lastSample)
} else {
await analyzer.cancelAndFinishNow()
}
return try await transcription
}
The async let starts collecting transcription results while the analyzer processes the file. When analysis finishes, finalizeAndFinish(through:) lets the analyzer flush remaining results through the final audio sample.
Then the function returns the collected AttributedString.
Why AttributedString?
The result text is not just a plain String. SpeechTranscriber.Result.text is an AttributedString, which gives the framework room to include speech metadata alongside the text.
The basic .transcription preset in this post is enough for displayable transcript text. If you want timing-backed UI later, use a time-indexed preset or request the audioTimeRange attribute explicitly. That is what lets you build features like:
- Tap a sentence and jump audio playback to that part.
- Highlight words while the audio plays.
- Keep volatile live results visually separate from final results.
For a first file transcription feature, returning an AttributedString is still convenient because SwiftUI can display it directly with Text.
Checking language assets
Speech transcription is on-device, but the model assets may need to be installed. Apple’s example checks supported and installed locales, then downloads missing assets with AssetInventory.
One small helper can check availability, find an equivalent supported locale, and install assets when needed:
enum TranscriptionSetupError: Error {
case speechUnavailable
case localeUnsupported
case assetUnavailable
case assetDownloadInProgress
}
func makeReadyTranscriber(locale: Locale) async throws -> SpeechTranscriber {
guard SpeechTranscriber.isAvailable else {
throw TranscriptionSetupError.speechUnavailable
}
guard let supportedLocale = await SpeechTranscriber.supportedLocale(
equivalentTo: locale
) else {
throw TranscriptionSetupError.localeUnsupported
}
let transcriber = SpeechTranscriber(
locale: supportedLocale,
preset: .transcription
)
switch await AssetInventory.status(forModules: [transcriber]) {
case .installed:
return transcriber
case .supported:
if let request = try await AssetInventory.assetInstallationRequest(
supporting: [transcriber]
) {
try await request.downloadAndInstall()
}
guard await AssetInventory.status(forModules: [transcriber]) == .installed else {
throw TranscriptionSetupError.assetDownloadInProgress
}
return transcriber
case .downloading:
throw TranscriptionSetupError.assetDownloadInProgress
case .unsupported:
throw TranscriptionSetupError.assetUnavailable
@unknown default:
throw TranscriptionSetupError.assetUnavailable
}
}
For a polished app, surface the download progress to the user and avoid starting a long transcription job until the asset state is installed. Treat .downloading as a retryable setup state rather than as permission to start analysis.
File transcription vs live transcription
File transcription is the cleanest place to learn the API because you avoid audio session setup. Live transcription adds a few more steps:
- Add
NSMicrophoneUsageDescriptionand request microphone permission withAVAudioApplication. - Configure
AVAudioSessionon iOS. - Use
AVAudioEngineto produce buffers. - Convert buffers to the analyzer’s preferred format.
- Feed buffers into the analyzer.
- Separate volatile results from finalized results to avoid duplicates.
The mental model stays the same, though. Configure modules, start the analyzer, process results from the async sequence, and finalize the stream when input ends.
Further reading:
- WWDC25 SpeechAnalyzer session: https://developer.apple.com/videos/play/wwdc2025/277/
- Speech framework documentation: https://developer.apple.com/documentation/Speech