如何利用 Swift(编程语言) 和 SwiftUI 框架的 Preview(预览功能) 来制作一个帮助你进行日语影子跟读(Shadowing)的练习工具
时间: 2025-09-07 05:26:40
(部分内容来自网络,其真实性存疑,为了避免对您造成误导,请谨慎甄别。)
好的,我们来详细说明一下如何利用 Swift(编程语言) 和 SwiftUI 框架的 Preview(预览功能) 来制作一个帮助你进行日语影子跟读(Shadowing)的练习工具。
这个应用的核心功能是:播放一段日语音频,同时在屏幕上动态显示当前正在说的文本(就像卡拉OK一样),帮助你进行眼和耳的同步跟读训练。
我们将使用 SwiftUI 来实现界面和逻辑,并充分利用 SwiftUI Preview 来实时调试界面布局。
第一步:项目概述与设计
功能需求:
1. 音频播放控制: 播放、暂停、跳转。
2. 文本同步高亮: 根据音频的当前播放时间,高亮对应的文本。
3. 文本数据: 需要准备一段日语文本和对应的时间戳。
技术要点:
* AVPlayer:用于播放远程或本地的音频文件。
* ScrollViewReader 和 ScrollView:用于在文本很长时自动滚动到当前高亮句子的位置。
* Timer:定时器,用于不断检查音频的当前播放时间,并更新UI。
* SwiftUI Preview:用于实时设计和调整文本高亮、布局等界面效果。
第二步:数据模型 (Model)
首先,我们需要定义数据结构。我们需要将完整的文本拆分成句子或段落,并为每一段关联一个开始时间戳。
import Foundation
// 单句模型 struct Sentence: Identifiable, Decodable { var id: UUID = UUID() let startTime: Double // 开始时间(秒) let text: String // 该时间点对应的句子
// CodingKeys 用于JSON解码(如果从文件加载数据) enum CodingKeys: String, CodingKey { case startTime, text } }
// 跟读会话模型,包含整个音频和文本信息 struct ShadowingSession: Decodable { let audioFileName: String let sentences: [Sentence] }
你可以创建一个 session.json 文件来存储你的音频和文本数据。
{
"audioFileName": "japanese_dialogue.mp3",
"sentences": [
{ "startTime": 0.0, "text": "こんにちは。" },
{ "startTime": 2.5, "text": "今日はいい天気ですね。" },
{ "startTime": 6.0, "text": "どこかへ出かけませんか?" },
{ "startTime": 10.0, "text": "ええ、そうしましょう。" }
]
}第三步:创建播放器管理器 (ViewModel)
这是一个关键类,它负责管理音频播放、跟踪时间,并发布当前状态给UI。
import Foundation import AVFoundation import Combine
class ShadowingPlayerViewModel: ObservableObject { // 播放器 private var player: AVPlayer? // 定时器,用于更新当前时间 private var timer: AnyCancellable? // 发布的属性,UI会监听这些变化 @Published var isPlaying = false @Published var currentTime: Double = 0.0 @Published var currentSentence: Sentence? let session: ShadowingSession init(session: ShadowingSession) { self.session = session // 初始化播放器 if let url = Bundle.main.url(forResource: session.audioFileName, withExtension: nil) { player = AVPlayer(url: url) } // 设置定时器,每秒更新60次(更流畅) timer = Timer.publish(every: 0.016, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in self?.updateCurrentTime() } } // 更新当前时间,并查找对应的句子 private func updateCurrentTime() { guard let player = player else { return } currentTime = player.currentTime().seconds // 根据当前时间找到最后一个开始时间小于等于currentTime的句子 // 这里假设sentences是按startTime升序排列的 if let sentence = session.sentences.last(where: { $0.startTime <= currentTime }) { currentSentence = sentence } } // 播放/暂停 func playPause() { guard let player = player else { return } if player.timeControlStatus == .playing { player.pause() isPlaying = false } else { player.play() isPlaying = true } } // 跳转到特定句子的开始时间 func seek(to sentence: Sentence) { player?.seek(to: CMTime(seconds: sentence.startTime, preferredTimescale: 1)) // 如果当前是暂停状态,跳转后自动播放 if !isPlaying { playPause() } } }
第四步:构建用户界面 (View)
现在我们来创建主界面,使用 SwiftUI Preview 来实时查看效果。
import SwiftUI
struct ShadowingView: View { @StateObject var viewModel: ShadowingPlayerViewModel var body: some View { VStack(spacing: 20) { // 1. 播放控制按钮 Button(action: { viewModel.playPause() }) { Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") .font(.system(size: 60)) } // 2. 文本显示区域 ScrollViewReader { scrollProxy in ScrollView { LazyVStack(alignment: .leading, spacing: 15) { ForEach(viewModel.session.sentences) { sentence in Text(sentence.text) .font(.title2) .padding(8) .background( // 如果是当前句子,高亮为黄色 viewModel.currentSentence?.id == sentence.id ? Color.yellow.opacity(0.5) : Color.clear ) .cornerRadius(8) .id(sentence.id) // 必须为可滚动视图设置id .onTapGesture { // 点击某句可以跳转到那句 viewModel.seek(to: sentence) } } } .padding() } .frame(maxHeight: 300) // 当当前句子变化时,自动滚动到该句子 .onChange(of: viewModel.currentSentence) { newSentence in withAnimation(.easeInOut(duration: 0.5)) { if let sentence = newSentence { scrollProxy.scrollTo(sentence.id, anchor: .center) } } } } // 3. 时间显示(可选) Text("$viewModel.currentTime, specifier: "%.1f")秒") .font(.body.monospacedDigit()) Spacer() } .padding() } }
第五步:利用 SwiftUI Preview 进行预览和调试
这是 SwiftUI 最强大的功能之一。我们可以在另一台模拟器或甚至不运行整个App的情况下,实时调整和预览界面。
// 为Preview提供一个假的Session #if DEBUG let previewSession = ShadowingSession( audioFileName: "preview_audio.mp3", sentences: [ Sentence(startTime: 0.0, text: "これはプレビュー用のテキストです。"), Sentence(startTime: 3.0, text: "SwiftUIのPreview機能はとても便利です。"), Sentence(startTime: 7.0, text: "リアルタイムでUIの確認ができます。") ] )
struct ShadowingView_Previews: PreviewProvider { static var previews: some View { // 在Preview中,我们不需要真的播放音频 ShadowingView(viewModel: ShadowingPlayerViewModel(session: previewSession)) } } #endif
在 Preview 中你能做什么:
1. 即时反馈: 修改 ShadowingView 的代码(例如字体大小、颜色、间距),Preview 面板会立即更新。
2. 调试布局: 检查文本高亮、滚动区域是否正常工作。你可以手动修改 @Published 的属性来模拟播放状态(例如,在 Preview 的代码里写 viewModel.currentSentence = previewSession.sentences[1] 来测试高亮和滚动)。
3. 多设备预览: 在 Preview 面板底部,你可以选择在不同的设备(iPhone, iPad)和深色/浅色模式下查看界面效果。
总结
通过结合 Swift 的强大多媒体处理能力和 SwiftUI Preview 的实时UI调试能力,你可以高效地构建一个非常实用的日语影子跟读工具。
开发流程:
1. 定义数据模型 (Sentence, ShadowingSession)。
2. 创建管理播放和状态的 ViewModel (ShadowingPlayerViewModel)。
3. 构建主界面 (ShadowingView)。
4. 不断使用 SwiftUI Preview 来调整和优化界面。
5. 最后,将 JSON 数据文件和音频文件添加到项目中,并初始化 ShadowingPlayerViewModel。
希望这个详细的指南对你有帮助!你可以在这个基础上继续添加更多功能,比如播放速度控制、循环播放某一句、生词本等。