本文我们要在visionOS内实现一个标题输出的动画效果。主要讲ViewModifier
协议,修饰符(modifier)应用于视图或另一个视图修饰符,生成原值的另一个版本。在希望创建一个可应用于不同视图的修饰符时可实现ViewModifier协议。
首先定义ViewModel
,本例中的模型比较简单,仅定了三个变量,分别表示当前文本、标题输出是否完成以及最终的标题文本。
import SwiftUI@Observable
class ViewModel {var titleText: String = ""var isTitleFinished: Bool = falsevar finalTitle: String = "第三回 托内兄如海荐西宾 接外孙贾母惜孤女"
}
因模型中有默认值且需要在程序运行的过程中进行修改,所以在入口文件中需要将模型注入到环境中:
import SwiftUI@main
struct visionOSDemoApp: App {@State private var model = ViewModel()var body: some Scene {WindowGroup() {ContentView().environment(model)}}
}
接下来就是本文的重点了,我们需要自定义一个文本修饰符。虽然可以直接将修饰符应用于视图,但更常见和地道的做法是使用修饰符来定义一个View
来包装这个视图修饰符。我们在代码里就是这么做的,在视图中我们传入了5个变量,text
和isFinished
是需要进行修改的,所以使用了Binding
,cursor
定义了光标,默认使用了常见的|
,isAnimated
表示是否显示动画。
在TypeTextModifier
中可以看到,如果isAnimated
为false
,就直接显示最终文本。而在任务中有两个for
循环,分别设置初始的光标闪烁效果以及后续逐个文字和光标交替输出的效果,最后等待片刻,标记输出结束。
import SwiftUIextension View {func typeText(text: Binding<String>,finalText: String,isFinished: Binding<Bool>,curor: String = "|",isAnimated: Bool = true) -> some View {self.modifier(TypeTextModifier(text: text,finalText: finalText,isFinished: isFinished,cursor: curor,isAnimated: isAnimated))}
}private struct TypeTextModifier: ViewModifier {@Binding var text: Stringvar finalText: String@Binding var isFinished: Boolvar cursor: Stringvar isAnimated: Boolfunc body(content: Content) -> some View {content.onAppear {if isAnimated == false {text = finalTextisFinished = true}}.task {guard isAnimated else { return }// Blink the cursor a few timesfor _ in 1...2 {text = cursortry? await Task.sleep(for: .milliseconds(500))text = ""try? await Task.sleep(for: .milliseconds(200))}// Type out the titlefor index in finalText.indices {text = String(finalText.prefix(through: index)) + cursorlet milliseconds = (1 + UInt64.random(in: 0...1)) * 100try? await Task.sleep(for: .milliseconds(milliseconds))}// Wrap up the title sequencetry? await Task.sleep(for: .milliseconds(400))text = finalTextisFinished = true}}
}
ContentView
内容如下:
struct ContentView: View {@Environment(ViewModel.self) private var modelvar body: some View {@Bindable var model = modelNavigationStack {VStack {Spacer()VStack {Text(model.finalTitle).monospaced().font(.system(size: 50, weight: .bold)).padding(.horizontal, 40).hidden().overlay(alignment: .leading) {Text(model.titleText).monospaced().font(.system(size: 50, weight: .bold)).padding(.leading, 40)}Text("林黛玉进贾府").font(.title).padding(.top, 10).opacity(model.isTitleFinished ? 1 : 0)}Spacer()}.typeText(text: $model.titleText, finalText: model.finalTitle, isFinished: $model.isTitleFinished, isAnimated: !model.isTitleFinished)}}
}
这里在屏幕中央输出两段文本,第一段会以修饰符的动画效果进行输出直至结束,第二段在第一段文本输出完成后显示。
示例代码:GitHub仓库
其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记