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:

  • SpeechAnalyzer manages the analysis session.
  • SpeechTranscriber is 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 NSMicrophoneUsageDescription and request microphone permission with AVAudioApplication.
  • Configure AVAudioSession on iOS.
  • Use AVAudioEngine to 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: