Speech Recognition

by dpearson26998d90fd121a26No license1.1K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

Transcribe speech to text using Apple's Speech framework. Use when implementing live microphone transcription with AVAudioEngine, recognizing recorded audio files, handling speech and microphone authorization, choosing on-device vs server-backed SFSpeechRecognizer behavior, or adopting SpeechAnalyzer, SpeechTranscriber, DictationTranscriber, AssetInventory, and async result streams on iOS 26+.

Instructions only

Speech Recognition

Transcribe live and pre-recorded audio to text using Apple's Speech framework. Covers SpeechAnalyzer / SpeechTranscriber (iOS 26+) and SFSpeechRecognizer (iOS 10+) fallback guidance.

Scope boundary: Use this skill for speech-to-text recognition, speech authorization, microphone capture plumbing, and result handling. Hand off text analysis, language identification after transcription, sentiment, embeddings, and translation to natural-language; hand off audio playback UI to avkit; hand off summarization or generation over transcripts to apple-on-device-ai.

Contents

SpeechAnalyzer Strategy (iOS 26+)

Use SpeechAnalyzer for modern iOS 26+ speech analysis, especially long-form recordings, live transcription, time-indexed transcripts, and fully on-device flows. Keep SFSpeechRecognizer for iOS 10+ deployment targets, server-backed locale coverage, or existing callback/delegate implementations.

Read SpeechAnalyzer patterns [blocked] when implementing an iOS 26+ transcription pipeline, model asset handling, volatile results, or file/buffer examples.

SpeechAnalyzer setup checklist

  1. Choose the module:
    • SpeechTranscriber for the newer general-purpose on-device model.
    • DictationTranscriber when SpeechTranscriber is unavailable for the current device or locale and dictation-compatible support is acceptable.
    • SpeechDetector only in conjunction with a transcriber when voice activity detection is worth the accuracy/power tradeoff.
  2. Check support before creating the session:
    • SpeechTranscriber.isAvailable
    • SpeechTranscriber.supportedLocale(equivalentTo:)
    • SpeechTranscriber.installedLocales / supportedLocales when showing language choices.
  3. Pick a documented preset:
    • .transcription for basic accurate transcription.
    • .progressiveTranscription for live UI updates.
    • .timeIndexedProgressiveTranscription when playback highlighting needs audioTimeRange.
  4. Install required assets with AssetInventory.assetInstallationRequest.
  5. Convert live audio buffers to SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:) before yielding AnalyzerInput.
  6. Consume module results from their AsyncSequence in a separate task.
  7. Finish explicitly with finalizeAndFinish(through:), finalizeAndFinishThroughEndOfInput(), or cancelAndFinishNow().

Do not use an offlineTranscription preset; Apple does not document one. Finishing an AsyncStream input sequence does not finish the analyzer session.

SFSpeechRecognizer Setup

Creating a recognizer with locale

swift
import Speech
// Default locale (user's current language)let recognizer = SFSpeechRecognizer()
// Specific localelet recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
// Check if recognition is available for this localeguard let recognizer, recognizer.isAvailable else {    print("Speech recognition not available")    return}

Monitoring availability changes

swift
final class SpeechManager: NSObject, SFSpeechRecognizerDelegate {    private let recognizer = SFSpeechRecognizer()!
    override init() {        super.init()        recognizer.delegate = self    }
    func speechRecognizer(        _ speechRecognizer: SFSpeechRecognizer,        availabilityDidChange available: Bool    ) {        // Update UI — disable record button when unavailable    }}

Authorization

Request both speech recognition and microphone permissions before starting live transcription. Add these keys to Info.plist:

  • NSSpeechRecognitionUsageDescription
  • NSMicrophoneUsageDescription
swift
import Speechimport AVFoundation
func requestPermissions() async -> Bool {    let speechStatus = await withCheckedContinuation { continuation in        SFSpeechRecognizer.requestAuthorization { status in            continuation.resume(returning: status)        }    }    guard speechStatus == .authorized else { return false }
    let micStatus: Bool    if #available(iOS 17, *) {        micStatus = await AVAudioApplication.requestRecordPermission()    } else {        micStatus = await withCheckedContinuation { continuation in            AVAudioSession.sharedInstance().requestRecordPermission { granted in                continuation.resume(returning: granted)            }        }    }    return micStatus}

Live Microphone Transcription

The standard pattern: AVAudioEngine captures microphone audio → buffers are appended to SFSpeechAudioBufferRecognitionRequest → results stream in.

swift
import Speechimport AVFoundation
final class LiveTranscriber {    private let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!    private let audioEngine = AVAudioEngine()    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?    private var recognitionTask: SFSpeechRecognitionTask?
    func startTranscribing() throws {        // Cancel any in-progress task        recognitionTask?.cancel()        recognitionTask = nil
        // Configure audio session        let audioSession = AVAudioSession.sharedInstance()        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        // Create request        let request = SFSpeechAudioBufferRecognitionRequest()        request.shouldReportPartialResults = true        self.recognitionRequest = request
        // Start recognition task        recognitionTask = recognizer.recognitionTask(with: request) { result, error in            if let result {                let text = result.bestTranscription.formattedString                print("Transcription: \(text)")
                if result.isFinal {                    self.stopTranscribing()                }            }            if let error {                print("Recognition error: \(error)")                self.stopTranscribing()            }        }
        // Install audio tap        let inputNode = audioEngine.inputNode        let recordingFormat = inputNode.outputFormat(forBus: 0)        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) {            buffer, _ in            request.append(buffer)        }
        audioEngine.prepare()        try audioEngine.start()    }
    func stopTranscribing() {        audioEngine.stop()        audioEngine.inputNode.removeTap(onBus: 0)        recognitionRequest?.endAudio()        recognitionRequest = nil        recognitionTask?.cancel()        recognitionTask = nil    }}

Pre-Recorded Audio File Recognition

Use SFSpeechURLRecognitionRequest for audio files on disk:

swift
func transcribeFile(at url: URL) async throws -> String {    guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else {        throw SpeechError.unavailable    }    let request = SFSpeechURLRecognitionRequest(url: url)    request.shouldReportPartialResults = false
    return try await withCheckedThrowingContinuation { continuation in        var didResume = false        recognizer.recognitionTask(with: request) { result, error in            guard !didResume else { return }            if let error {                didResume = true                continuation.resume(throwing: error)            } else if let result, result.isFinal {                didResume = true                continuation.resume(                    returning: result.bestTranscription.formattedString                )            }        }    }}

On-Device vs Server Recognition

SFSpeechRecognizer can use on-device recognition for supported locales on iOS 13+. If supportsOnDeviceRecognition is false, the recognizer requires a network connection. requiresOnDeviceRecognition only has effect when the recognizer supports it.

swift
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!
// Check if on-device is supported for this localeif recognizer.supportsOnDeviceRecognition {    let request = SFSpeechAudioBufferRecognitionRequest()    request.requiresOnDeviceRecognition = true  // Force on-device}

SFSpeechRecognizer requests may still be a poor fit for long-form capture. Apple documents a roughly one-minute task limit for speech recognition and other service limits. For long recordings on iOS 26+, prefer SpeechAnalyzer; otherwise chunk or restart recognition before the limit and preserve transcript state across tasks.

Handling Results

Partial vs final results

With shouldReportPartialResults, replace the displayed partial transcript until SFSpeechRecognitionResult.isFinal commits it. This is separate from SpeechTranscriber.Result.isFinal, whose volatile attributed range must be replaced by the final result for that range. The live example and references/speechanalyzer-patterns.md [blocked] contain the canonical loops.

Accessing alternative transcriptions and confidence

swift
recognizer.recognitionTask(with: request) { result, error in    guard let result else { return }
    // Best transcription    let best = result.bestTranscription
    // All alternatives (sorted by confidence, descending)    for transcription in result.transcriptions {        for segment in transcription.segments {            print("\(segment.substring): \(segment.confidence)")        }    }}

Adding punctuation (iOS 16+)

swift
let request = SFSpeechAudioBufferRecognitionRequest()request.addsPunctuation = true

Contextual strings

Improve recognition of domain-specific terms:

swift
let request = SFSpeechAudioBufferRecognitionRequest()request.contextualStrings = ["SwiftUI", "Xcode", "CloudKit"]

Common Mistakes

MistakeFix
Live audio requests only speech authorizationRequire both speech and microphone permission.
Recognizer availability is checked onceObserve delegate availability changes and model loss of service.
Final/error/rollover paths perform different cleanupUse one stopTranscribing() owner for engine stop, tap removal, endAudio, and task cancellation.
On-device mode is forced for every localeCheck supportsOnDeviceRecognition and provide a fallback.
One SFSpeechRecognizer task is used for long-form capturePrefer SpeechAnalyzer on iOS 26+ or roll bounded segments while preserving committed transcript.
Finishing analyzer input is treated as session completionExplicitly finalize through the last sample or cancel-and-finish.
Volatile SpeechAnalyzer results are appendedReplace the volatile range until a final result commits it.
A second recognition task starts before the first endsCancel/finish the active task and complete cleanup before replacement.

Load references/speechanalyzer-patterns.md [blocked] for complete analyzer finalization and volatile-result code.

Review Checklist

  • NSSpeechRecognitionUsageDescription is in Info.plist
  • NSMicrophoneUsageDescription is in Info.plist (if using live audio)
  • Authorization is requested before starting recognition
  • SFSpeechRecognizerDelegate is set to handle availabilityDidChange
  • Audio engine is stopped and tap removed when recognition ends
  • recognitionRequest.endAudio() is called when done recording
  • Previous recognitionTask is canceled before starting a new one
  • supportsOnDeviceRecognition is checked before requiring on-device mode
  • Partial results are handled separately from final (isFinal) results
  • SFSpeechRecognizer one-minute/service limits are accounted for
  • For iOS 26+: AssetInventory assets are installed before using SpeechAnalyzer
  • For iOS 26+: SpeechTranscriber.isAvailable and locale support are checked
  • For iOS 26+: live buffers are converted to the analyzer-compatible format
  • For iOS 26+: analyzer sessions are explicitly finalized or canceled
  • For iOS 26+: volatile results are replaced by finalized results, not duplicated

References

Source and attribution

Source:dpearson2699/swift-ios-skillsinskills/speech-recognitionat commit8d90fd1

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from dpearson2699/swift-ios-skills

Widgetkit

dpearson2699

Guides implementing, reviewing, and improving WidgetKit widgets and controls for iOS, iPadOS, watchOS, and CarPlay.

Software Development1.1Kupdated 2 months ago

Weatherkit

dpearson2699

Guides iOS developers in fetching WeatherKit forecasts, alerts, and attribution using WeatherService.

Software Development1.1Kupdated 2 months ago

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

Awaiting classification1.1Kupdated 2 months ago

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

Awaiting classification1.1Kupdated 2 months ago

Tabletopkit

dpearson2699

Guides building multiplayer spatial board games on visionOS with Apple's TabletopKit and RealityKit.

Software Development1.1Kupdated 2 months ago

Swiftui Webkit

dpearson2699

Guides embedding and controlling web content in SwiftUI apps with WebKit for SwiftUI on iOS 26 and later.

Software Development1.1Kupdated 2 months ago