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
- 1Permissions: Request permissions clearly and appropriately
- 2Feedback: Provide visual feedback during recording
- 3Error handling: Handle errors gracefully
- 4Battery: Consider battery consumption
- 5Privacy: Inform users about data usage
- 6Testing: Test on real devices
- 7Accessibility: Make voice features accessible
- 8Network: Handle offline scenarios
Speech recognition opens new possibilities for app interaction!