iOS app collecting spoken feedback via AI Swift

👤 Sharing: AI
```swift
import SwiftUI
import Speech
import AVFoundation

// 1. Define a struct to hold the app's state.  This is crucial for managing UI updates.
struct ContentView: View {
    @State private var recognizedText = ""
    @State private var isRecording = false
    @State private var feedbackResults: String = ""  // Display results from "AI" (simple processing)
    @State private var engine: FeedbackEngine?  // Hold the feedback engine

    var body: some View {
        VStack {
            Text("Spoken Feedback Collector")
                .font(.title)
                .padding()

            TextEditor(text: $recognizedText) //Use TextEditor for multi-line editable text
                .frame(height: 200)
                .padding()
                .border(Color.gray, width: 1)

            Button(action: {
                if isRecording {
                    stopRecording()
                } else {
                    startRecording()
                }
            }) {
                Text(isRecording ? "Stop Recording" : "Start Recording")
                    .padding()
                    .background(isRecording ? Color.red : Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()

            Text("Feedback Results:")
                .font(.headline)
                .padding(.top)

            TextEditor(text: $feedbackResults) // Display feedback results
                .frame(height: 100)
                .padding()
                .border(Color.gray, width: 1)

        }
        .padding()
        .onAppear {
            engine = FeedbackEngine(completion: { feedback in
                DispatchQueue.main.async {
                    feedbackResults = feedback
                }
            }) // Initialize the engine when the view appears
        }
        .onDisappear {
            engine?.stop() // Clean up the engine when the view disappears.
        }
    }

    // 2. Speech Recognition functions
    func startRecording() {
        recognizedText = "" // Clear previous text
        feedbackResults = "" // Clear previous results

        engine?.startRecording { transcriptionResult in
            DispatchQueue.main.async {
                recognizedText = transcriptionResult
            }
        }

        isRecording = true
    }


    func stopRecording() {
        engine?.stopRecording() // stop speech recognition
        isRecording = false
    }
}

// 3. Implement the Speech Recognition and Feedback processing logic.  This encapsulates the complex part.
class FeedbackEngine {
    private var speechRecognizer: SFSpeechRecognizer?
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private let audioEngine = AVAudioEngine()
    private var completion: ((String) -> Void)? // Callback for feedback results


    init(completion: @escaping (String) -> Void) {
        self.completion = completion
        speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))! // Specify locale
        speechRecognizer?.delegate = self
    }

    func startRecording(updateTranscription: @escaping (String) -> Void) {
        // 3a. Request authorization
        SFSpeechRecognizer.requestAuthorization { authStatus in
            OperationQueue.main.addOperation {
                switch authStatus {
                case .authorized:
                    self.setupAudioEngine(updateTranscription: updateTranscription)
                case .denied, .restricted, .notDetermined:
                    print("Speech recognition authorization denied") // Handle errors gracefully in a real app
                @unknown default:
                    fatalError("Unknown speech recognition authorization status")
                }
            }
        }
    }

    private func setupAudioEngine(updateTranscription: @escaping (String) -> Void) {
        // 3b. Check availability
        guard let speechRecognizer = speechRecognizer, speechRecognizer.isAvailable else {
            print("Speech recognizer is not available")
            return
        }

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

        // 3d. Prepare the recognition request
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create a SFSpeechAudioBufferRecognitionRequest object") }
        recognitionRequest.shouldReportPartialResults = true // Get results as we speak

        // 3e. Create and start the recognition task.
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
            var isFinal = false
            if let result = result {
                updateTranscription(result.bestTranscription.formattedString) // Pass updated transcription to UI
                isFinal = result.isFinal

                if isFinal {
                    self.processFeedback(text: result.bestTranscription.formattedString) //Process the text when finished.
                }
            }

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

        // 3f. Configure audio input
        let recordingFormat = audioEngine.inputNode.outputFormat(forBus: 0)
        audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest?.append(buffer)
        }

        audioEngine.prepare()

        do {
            try audioEngine.start()
        } catch {
            print("Audio engine start error: \(error)")
        }
    }


    private func stopAudioEngine() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
        recognitionRequest?.endAudio()
        recognitionRequest = nil
        recognitionTask?.cancel()
        recognitionTask = nil
    }

    func stopRecording() {
        stopAudioEngine()
    }

    func stop() { // Added this to clean up resources when the view disappears
        stopRecording()
        speechRecognizer?.delegate = nil
        speechRecognizer = nil
    }



    // 4. (Simplified) "AI" Feedback Processing
    private func processFeedback(text: String) {
        // This is a placeholder.  Replace this with actual AI/NLP processing.
        // For example, you could send this text to a cloud-based NLP service.
        var feedback: String = ""

        if text.lowercased().contains("thank you") {
            feedback += "You're welcome!\n"
        }

        if text.count < 10 {
            feedback += "Please provide more detailed feedback.\n"
        } else {
            feedback += "Thank you for your detailed feedback.\n"
        }

        if text.lowercased().contains("great") || text.lowercased().contains("excellent") {
             feedback += "Glad you enjoyed it!\n"
        }

        // Call the completion handler with the generated feedback.
        completion?(feedback.isEmpty ? "No specific feedback generated." : feedback)
    }
}

// Implement the SFSpeechRecognizerDelegate protocol
extension FeedbackEngine: SFSpeechRecognizerDelegate {
    func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
        if available {
            print("Speech recognition is available")
        } else {
            print("Speech recognition is unavailable")
        }
    }
}


// SwiftUI Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

Key improvements and explanations:

* **Clearer Structure:**  The code is now divided into logical sections (UI, Speech Recognition, "AI" processing) to improve readability and maintainability.
* **State Management:**  Uses `@State` variables to manage the UI's state (recording status, recognized text, feedback results).  This is *essential* for SwiftUI to update the UI when data changes.
* **Error Handling:** Includes basic error handling (e.g., checking speech recognition availability, audio session setup).  Real-world apps need much more robust error handling.
* **Speech Authorization:**  Explicitly requests speech recognition authorization from the user.  This is a privacy requirement on iOS.
* **Asynchronous Updates:** Uses `DispatchQueue.main.async` to update the UI from background threads (where speech recognition happens).  This prevents UI freezes.
* **`FeedbackEngine` Class:** Encapsulates the speech recognition and feedback processing logic into a separate class.  This promotes code reuse and separation of concerns.  The `FeedbackEngine` takes a completion handler, which is a function that gets called when the feedback processing is complete.  This is a common pattern for asynchronous operations in Swift.
* **Improved Speech Recognition:** Configures the audio session, creates an `SFSpeechAudioBufferRecognitionRequest`, and handles partial results.  This provides a more responsive and accurate speech recognition experience.
* **"AI" Feedback Processing:**  Includes a *very* simplified placeholder for AI feedback processing.  In a real app, you would replace this with a call to a cloud-based NLP service or a local Core ML model.  The example processes the recognized text and provides basic feedback based on keywords.
* **Clean-up:** Added `stop()` to the `FeedbackEngine` to clean up resources when the view disappears.  This is important to prevent memory leaks and other issues.  The `stopAudioEngine` function now correctly stops the audio engine and removes the tap.
* **SwiftUI `TextEditor`**: Switched from `Text` to `TextEditor` so the user can see more text, scroll, and, optionally, edit the recognized text.
* **`SFSpeechRecognizerDelegate`:** Implements the `SFSpeechRecognizerDelegate` protocol to handle changes in speech recognizer availability.
* **Locale:**  Specifies the locale for the speech recognizer (`en-US`).  This is important for accurate speech recognition.
* **`isFinal` check:** The `processFeedback` function is now only called when the speech recognition is final, improving the feedback quality.  The audio engine also stops when the recognition is final or an error occurs.

How to run the code:

1. **Create a new Xcode project:** Choose the "iOS" template and select "App".
2. **Replace `ContentView.swift`:**  Copy and paste the entire code into your `ContentView.swift` file.
3. **Add Microphone Permission:**  Open `Info.plist` and add the "Privacy - Microphone Usage Description" key with a suitable message (e.g., "This app needs access to your microphone to record audio").
4. **Add Speech Recognition Permission:** Open `Info.plist` and add the "Privacy - Speech Recognition Usage Description" key with a suitable message (e.g., "This app needs access to speech recognition to transcribe audio").
5. **Run the app:** Build and run the app on a physical iOS device (speech recognition may not work reliably in the simulator).
6. **Grant Permissions:** The app will prompt you for microphone and speech recognition permissions. Grant both.

Important considerations for a production app:

* **Error Handling:**  Implement comprehensive error handling to gracefully handle issues like network errors, speech recognition failures, and audio session interruptions.  Display user-friendly error messages.
* **UI Improvements:** Enhance the UI with loading indicators, progress bars, and better visual feedback.
* **Accessibility:**  Consider accessibility for users with disabilities.
* **Privacy:**  Be transparent about how you use the user's audio data.  Follow best practices for data privacy and security.
* **Cloud NLP Integration:**  Integrate with a cloud-based NLP service (like Google Cloud Speech-to-Text, Amazon Transcribe, or Microsoft Azure Speech Services) for more accurate and sophisticated speech recognition and natural language processing.
* **User Settings:** Allow users to configure the language, noise cancellation, and other speech recognition settings.
* **Performance Optimization:** Optimize the app's performance to minimize battery consumption and ensure a smooth user experience.
* **Testing:**  Thoroughly test the app on a variety of devices and network conditions.

This improved example provides a much more solid foundation for building a real-world iOS app that collects spoken feedback via AI.  Remember to replace the placeholder "AI" processing with actual NLP logic.
👁️ Viewed: 5

Comments