Last active
October 26, 2025 15:13
-
-
Save lanserxt/77780783adfa179c7a009042b9cf5a95 to your computer and use it in GitHub Desktop.
iOS 26: SpeechRecognizer example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // SpeechTranscribeView.swift | |
| // WWDC25Demo | |
| // | |
| // Created by Anton Gubarenko on 20.07.2025. | |
| // | |
| import SwiftUI | |
| import Playgrounds | |
| import Speech | |
| /// Demo View for SpeechRecognizer | |
| struct SpeechTranscribeView: View { | |
| @State private var transcribedText: AttributedString = "" | |
| var body: some View { | |
| VStack { | |
| Text("Transcribe speech") | |
| ScrollView(.vertical, content: { | |
| Text(transcribedText) | |
| .frame(maxHeight: .infinity) | |
| .multilineTextAlignment(.leading) | |
| }) | |
| Spacer() | |
| }.task{ | |
| do { | |
| try await transcribeFile() | |
| } catch { | |
| print(error) | |
| } | |
| } | |
| } | |
| /// Main recognition task | |
| private func transcribeFile() async throws { | |
| var finalStr: AttributedString = "" | |
| let fileUrl = try loadAudioFile(named: "dragon_sample", withExtension: "mp3") | |
| let locale = Locale(identifier: "it_IT") | |
| let transcriber = transcriber(for: locale) | |
| print("transcriber ready") | |
| try await ensureModel(transcriber: transcriber, locale: locale) | |
| print("ensureModel ready") | |
| let analyzer = SpeechAnalyzer(modules: [transcriber]) | |
| if let lastSample = try await analyzer.analyzeSequence(from: AVAudioFile(forReading: fileUrl)) { | |
| try await analyzer.finalizeAndFinish(through: lastSample) | |
| } else { | |
| await analyzer.cancelAndFinishNow() | |
| } | |
| for try await recResponse in transcriber.results { | |
| if recResponse.isFinal { | |
| finalStr.append(recResponse.text) | |
| } | |
| } | |
| transcribedText = finalStr | |
| } | |
| /// Loading audio file | |
| /// - Parameters: | |
| /// - name: file name | |
| /// - ext: extension | |
| /// - Returns: URL of file | |
| private func loadAudioFile(named name: String, withExtension ext: String) throws -> URL { | |
| guard let url = Bundle.main.url(forResource: name, withExtension: ext) else { | |
| throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Audio file not found in bundle."]) | |
| } | |
| return url | |
| } | |
| /// Transcriber for locale | |
| /// - Parameter locale: locale to recognize | |
| /// - Returns: Transcriber | |
| private func transcriber(for locale: Locale) -> SpeechTranscriber { | |
| SpeechTranscriber(locale: locale, | |
| transcriptionOptions: [], | |
| reportingOptions: [.volatileResults], | |
| attributeOptions: [.audioTimeRange]) | |
| } | |
| /// Chekc that model exists | |
| /// - Parameters: | |
| /// - transcriber: transcriber to use for this model | |
| /// - locale: locale to recognize | |
| private func ensureModel(transcriber: SpeechTranscriber, locale: Locale) async throws { | |
| guard await supported(locale: locale) else { | |
| throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Locale not supported"]) | |
| } | |
| if await installed(locale: locale) { | |
| return | |
| } else { | |
| try await downloadIfNeeded(for: transcriber) | |
| } | |
| } | |
| /// Is locale supported | |
| /// - Parameter locale: locale to recognize | |
| /// - Returns: true if supported, fasle - else | |
| private func supported(locale: Locale) async -> Bool { | |
| print(SpeechTranscriber.isAvailable) | |
| guard SpeechTranscriber.isAvailable else {return false} | |
| let supported = await DictationTranscriber.supportedLocales | |
| print("Have locales \(supported)") | |
| return supported.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47)) | |
| } | |
| /// Is locale file installed locally | |
| /// - Parameter locale: locale to recognize | |
| /// - Returns: true is installed locally | |
| private func installed(locale: Locale) async -> Bool { | |
| let installed = await Set(SpeechTranscriber.installedLocales) | |
| return installed.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47)) | |
| } | |
| /// Download module assets | |
| /// - Parameter module: module to use | |
| private func downloadIfNeeded(for module: SpeechTranscriber) async throws { | |
| if let downloader = try await AssetInventory.assetInstallationRequest(supporting: [module]) { | |
| try await downloader.downloadAndInstall() | |
| } | |
| } | |
| } | |
| #Preview { | |
| SpeechTranscribeView() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment