SwiftUI聊天应用语音转文字TextField残留旧转录内容问题求助
SwiftUI聊天应用语音转文字二次启动自动填充旧内容的修复方案
在SwiftUI聊天应用中实现了麦克风语音转文字功能,但存在异常:第二次长按麦克风启动新转录时,TextField会自动填充上一次的转录内容。尝试过启动新转录前清除转录内容、清空chatViewModel.currentInput等操作,问题仍未解决。
问题根源
核心问题是第一次的语音识别任务未被正确取消:即使停止了音频引擎,旧的识别任务仍在后台运行,当第二次启动转录时,旧任务的回调依然会更新currentInput,导致显示上一次的转录内容。此外,原代码未持有识别任务的引用,无法主动终止旧任务,同时启动时的状态重置不彻底。
修复方案
- 添加
SFSpeechRecognitionTask类型属性,持有当前识别任务,方便停止时主动取消 - 启动录音前强制清空
chatViewModel.currentInput和转录状态标记 - 停止录音时不仅停止音频引擎,还要取消识别任务并重置音频会话状态
修改后的完整代码
import Speech import SwiftUI struct ChatView: View { var chatId: UUID @State private var isTranscriptionLongEnough = false private let audioEngine = AVAudioEngine() @EnvironmentObject var chatViewModel: ChatViewModel // 新增:持有当前识别任务引用,用于主动取消 @State private var recognitionTask: SFSpeechRecognitionTask? var body: some View { VStack { ScrollViewReader { _ in ScrollView { VStack { ForEach(chatViewModel.chats.filter { $0.id == chatId }) { chat in ForEach(chat.messages) { message in messageView(message: message) } } } .padding() } } HStack { TextField("Enter a message...", text: $chatViewModel.currentInput) Button { if !chatViewModel.currentInput.isEmpty { chatViewModel.sendMessage(chatId: chatId) } } label: { Image(systemName: "paperplane") } Button {} label: { Image(systemName: "mic.circle.fill") } .onLongPressGesture(minimumDuration: 0.1, maximumDistance: .infinity, pressing: { isPressing in if isPressing { startRecording() } else { stopRecording() } }, perform: {}) } .padding() } } func startRecording() { // 启动前清空旧内容和状态 chatViewModel.currentInput = "" isTranscriptionLongEnough = false // 取消之前未完成的识别任务 recognitionTask?.cancel() recognitionTask = nil SFSpeechRecognizer.requestAuthorization { authStatus in if authStatus == .authorized { guard let recognizer = SFSpeechRecognizer(locale: Locale.current) else { return } let request = SFSpeechAudioBufferRecognitionRequest() // 持有识别任务引用,方便后续取消 recognitionTask = recognizer.recognitionTask(with: request) { result, error in if let result = result { let transcription = result.bestTranscription.formattedString if !transcription.isEmpty { isTranscriptionLongEnough = true chatViewModel.currentInput = transcription } } // 任务结束或出错时重置引用 if error != nil || result?.isFinal == true { self.recognitionTask = nil } } let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.record, mode: .default) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) 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() } catch { print("Error trying to transcribe audio: \(error.localizedDescription)") recognitionTask = nil } } } } func stopRecording() { // 停止音频引擎并移除输入Tap audioEngine.stop() audioEngine.inputNode.removeTap(onBus: 0) // 取消当前识别任务 recognitionTask?.cancel() recognitionTask = nil // 停用音频会话,释放资源 let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setActive(false) } catch { print("Error deactivating audio session: \(error.localizedDescription)") } if isTranscriptionLongEnough { chatViewModel.sendMessage(chatId: chatId) isTranscriptionLongEnough = false // 发送后清空输入框 chatViewModel.currentInput = "" } } func messageView(message: Message) -> some View { HStack { if message.role == .user { Spacer() } Text(message.content) .padding() .background(message.role == .user ? .blue : .gray.opacity(0.7)) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: 20)) if message.role == .assistant || message.role == .system { Spacer() } } } } struct ChatView_Previews: PreviewProvider { static var previews: some View { ChatView(chatId: UUID()) .environmentObject(ChatViewModel()) } }
内容的提问来源于stack exchange,提问作者Diego Perdomo
相关产品推荐
相关产品推荐

