Skip to content
All essays
iOSMarch 27, 202513 min

Swift Speech Recognition: Voice Control Implementation

Implement speech recognition in iOS apps. Speech framework, dictation, and voice commands.

Ü
Ümit Uz
Mobile & Full Stack Developer

Speech recognition enables users to interact with your app using voice. Learn to implement speech recognition in iOS apps.

Speech Framework Setup

Permission Request

swift
import Speech
import AVFoundation

class SpeechPermissionManager: ObservableObject {
    @Published var authorizationStatus: SFSpeechRecognizerAuthorizationStatus = .notDetermined

    func requestAuthorization() {
        SFSpeechRecognizer.requestAuthorization { status in
            DispatchQueue.main.async {
                self.authorizationStatus = status
            }
        }
    }

    func requestMicrophonePermission() async -> Bool {
        let status = await AVAudioSession.sharedInstance().requestRecordPermission()
        return status
    }
}

Basic Speech Recognition

Simple Speech Recognizer

swift
import Speech
import SwiftUI

class SpeechRecognizer: ObservableObject {
    @Published var recognizedText: String = ""
    @Published var isRecognizing = false

    private let audioEngine = AVAudioEngine()
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private let speechRecognizer: SFSpeechRecognizer?

    init() {
        speechRecognizer = SFSpeechRecognizer()
    }

    func startRecording() {
        guard let recognizer = speechRecognizer, recognizer.isAvailable else {
            print("Speech recognizer not available")
            return
        }

        // Configure audio session
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
            try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        } catch {
            print("Audio session error: \(error)")
            return
        }

        // Create recognition request
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest = recognitionRequest else {
            print("Unable to create recognition request")
            return
        }

        recognitionRequest.shouldReportPartialResults = true

        // Configure recognition task
        recognitionTask = recognizer.recognitionTask(with: recognitionRequest) { result, error in
            var isFinal = false

            if let result = result {
                DispatchQueue.main.async {
                    self.recognizedText = result.bestTranscription.formattedString
                }
                isFinal = result.isFinal
            }

            if error != nil || isFinal {
                self.stopRecording()
            }
        }

        // Configure audio input
        let inputNode = audioEngine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)

        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in
            self.recognitionRequest?.append(buffer)
        }

        audioEngine.prepare()
        do {
            try audioEngine.start()
            DispatchQueue.main.async {
                self.isRecognizing = true
            }
        } catch {
            print("Audio engine error: \(error)")
        }
    }

    func stopRecording() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)

        recognitionRequest?.endAudio()
        recognitionRequest = nil
        recognitionTask?.cancel()
        recognitionTask = nil

        DispatchQueue.main.async {
            self.isRecognizing = false
        }
    }
}

SwiftUI Integration

Speech Recognition View

swift
struct SpeechRecognitionView: View {
    @StateObject private var speechRecognizer = SpeechRecognizer()
    @StateObject private var permissionManager = SpeechPermissionManager()

    var body: some View {
        VStack(spacing: 20) {
            Text("Speech Recognition")
                .font(.title)
                .padding()

            TextEditor(text: $speechRecognizer.recognizedText)
                .frame(height: 200)
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(10)

            Button(action: {
                if speechRecognizer.isRecognizing {
                    speechRecognizer.stopRecording()
                } else {
                    speechRecognizer.startRecording()
                }
            }) {
                HStack {
                    Image(systemName: speechRecognizer.isRecognizing ? "mic.fill" : "mic")
                    Text(speechRecognizer.isRecognizing ? "Stop" : "Start")
                }
                .frame(maxWidth: .infinity)
                .padding()
                .background(speechRecognizer.isRecognizing ? Color.red : Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            .padding()

            if permissionManager.authorizationStatus == .notDetermined {
                Button("Request Permissions") {
                    permissionManager.requestAuthorization()
                }
                .padding()
            }
        }
        .onAppear {
            permissionManager.requestAuthorization()
        }
    }
}

Advanced Features

Multi-Language Support

swift
class MultiLanguageSpeechRecognizer: ObservableObject {
    @Published var recognizedText: String = ""
    @Published var selectedLanguage = "en-US"

    private let recognizers: [String: SFSpeechRecognizer]

    init() {
        recognizers = [
            "en-US": SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!,
            "es-ES": SFSpeechRecognizer(locale: Locale(identifier: "es-ES"))!,
            "fr-FR": SFSpeechRecognizer(locale: Locale(identifier: "fr-FR"))!,
            "de-DE": SFSpeechRecognizer(locale: Locale(identifier: "de-DE"))!
        ]
    }

    var currentRecognizer: SFSpeechRecognizer? {
        recognizers[selectedLanguage]
    }

    func changeLanguage(to language: String) {
        selectedLanguage = language
    }
}

Voice Commands

swift
class VoiceCommandProcessor: ObservableObject {
    @Published var lastCommand: String = ""

    private let commands: [String: () -> Void] = [
        "open settings": { print("Opening settings") },
        "show profile": { print("Showing profile") },
        "start timer": { print("Starting timer") },
        "send message": { print("Sending message") }
    ]

    func processVoiceCommand(_ text: String) {
        let lowercaseText = text.lowercased()

        for (command, action) in commands {
            if lowercaseText.contains(command) {
                DispatchQueue.main.async {
                    self.lastCommand = command
                }
                action()
                return
            }
        }
    }
}

Dictation Implementation

Text Field with Dictation

swift
struct DictationTextField: View {
    @State private var text: String = ""
    @StateObject private var speechRecognizer = SpeechRecognizer()

    var body: some View {
        VStack {
            TextField("Enter text", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
                .overlay(
                    HStack {
                        Spacer()
                        Button(action: {
                            if speechRecognizer.isRecognizing {
                                speechRecognizer.stopRecording()
                            } else {
                                speechRecognizer.startRecording()
                            }
                        }) {
                            Image(systemName: speechRecognizer.isRecognizing ? "mic.fill" : "mic")
                                .foregroundColor(.blue)
                        }
                        .padding(.trailing)
                    }
                )
        }
        .onChange(of: speechRecognizer.recognizedText) { _, newText in
            text = newText
        }
    }
}

Accessibility Integration

VoiceOver Integration

swift
struct AccessibleSpeechView: View {
    @StateObject private var speechRecognizer = SpeechRecognizer()

    var body: some View {
        VStack {
            Button(action: {
                if speechRecognizer.isRecognizing {
                    speechRecognizer.stopRecording()
                } else {
                    speechRecognizer.startRecording()
                }
            }) {
                Image(systemName: "mic.fill")
                    .font(.title)
                    .accessibilityLabel(
                        speechRecognizer.isRecognizing ? "Stop recording" : "Start recording"
                    )
            }

            Text(speechRecognizer.recognizedText)
                .accessibilityLabel("Recognized text")
        }
    }
}

Error Handling

Comprehensive Error Handling

swift
enum SpeechError: LocalizedError {
    case permissionDenied
    case recognizerUnavailable
    case audioSessionError(Error)
    case recognitionFailed(Error)

    var errorDescription: String? {
        switch self {
        case .permissionDenied:
            return "Speech recognition permission denied"
        case .recognizerUnavailable:
            return "Speech recognizer is not available"
        case .audioSessionError(let error):
            return "Audio session error: \(error.localizedDescription)"
        case .recognitionFailed(let error):
            return "Recognition failed: \(error.localizedDescription)"
        }
    }
}

extension SpeechRecognizer {
    func handleRecordingError(_ error: Error) {
        // Handle specific errors
        if let speechError = error as? SpeechError {
            print("Speech error: \(speechError.errorDescription ?? "")")
        } else {
            print("Unknown error: \(error.localizedDescription)")
        }
    }
}

Best Practices

  1. 1Permissions: Request permissions clearly and appropriately
  2. 2Feedback: Provide visual feedback during recording
  3. 3Error handling: Handle errors gracefully
  4. 4Battery: Consider battery consumption
  5. 5Privacy: Inform users about data usage
  6. 6Testing: Test on real devices
  7. 7Accessibility: Make voice features accessible
  8. 8Network: Handle offline scenarios

Speech recognition opens new possibilities for app interaction!

Related essays

Next essay
Swift Location Services: GPS and Mapping