visionOS空间计算实战开发教程Day 11 标题动画

简介: 本文我们要在visionOS内实现一个标题输出的动画效果。主要讲ViewModifier协议,修饰符(modifier)应用于视图或另一个视图修饰符,生成原值的另一个版本。在希望创建一个可应用于不同视图的修饰符时可实现ViewModifier协议。

本文我们要在visionOS内实现一个标题输出的动画效果。主要讲ViewModifier协议,修饰符(modifier)应用于视图或另一个视图修饰符,生成原值的另一个版本。在希望创建一个可应用于不同视图的修饰符时可实现ViewModifier协议。

首先定义ViewModel,本例中的模型比较简单,仅定了三个变量,分别表示当前文本、标题输出是否完成以及最终的标题文本。

import SwiftUI
@Observable
class ViewModel {
    var titleText: String = ""
    var isTitleFinished: Bool = false
    var finalTitle: String = "第三回 托内兄如海荐西宾 接外孙贾母惜孤女"
}

因模型中有默认值且需要在程序运行的过程中进行修改,所以在入口文件中需要将模型注入到环境中:

import SwiftUI
@main
struct visionOSDemoApp: App {
    @State private var model = ViewModel()
    var body: some Scene {
        WindowGroup() {
            ContentView()
                .environment(model)
        }
    }
}

接下来就是本文的重点了,我们需要自定义一个文本修饰符。虽然可以直接将修饰符应用于视图,但更常见和地道的做法是使用修饰符来定义一个View来包装这个视图修饰符。我们在代码里就是这么做的,在视图中我们传入了5个变量,textisFinished是需要进行修改的,所以使用了Bindingcursor定义了光标,默认使用了常见的|isAnimated表示是否显示动画。

TypeTextModifier中可以看到,如果isAnimatedfalse,就直接显示最终文本。而在任务中有两个for循环,分别设置初始的光标闪烁效果以及后续逐个文字和光标交替输出的效果,最后等待片刻,标记输出结束。

import SwiftUI
extension 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: String
    var finalText: String
    @Binding var isFinished: Bool
    var cursor: String
    var isAnimated: Bool
    func body(content: Content) -> some View {
        content
            .onAppear {
                if isAnimated == false {
                    text = finalText
                    isFinished = true
                }
            }
            .task {
                guard isAnimated else { return }
                // Blink the cursor a few times
                for _ in 1...2 {
                    text = cursor
                    try? await Task.sleep(for: .milliseconds(500))
                    text = ""
                    try? await Task.sleep(for: .milliseconds(200))
                }
                // Type out the title
                for index in finalText.indices {
                    text = String(finalText.prefix(through: index)) + cursor
                    let milliseconds = (1 + UInt64.random(in: 0...1)) * 100
                    try? await Task.sleep(for: .milliseconds(milliseconds))
                }
                // Wrap up the title sequence
                try? await Task.sleep(for: .milliseconds(400))
                text = finalText
                isFinished = true
            }
    }
}

ContentView内容如下:

struct ContentView: View {
    @Environment(ViewModel.self) private var model
    var body: some View {
        @Bindable var model = model
        NavigationStack {
            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开发学习笔记

相关文章
|
存储 编解码 缓存
FFmpeg之旅:深入解析FFplay源码
FFmpeg之旅:深入解析FFplay源码
1291 0
|
vr&ar
visionOS空间计算实战开发教程Day 6 拖拽和点击
在之前的学习中我们在空间中添加了3D模型,但在初始摆放后就无法再对其进行移动或做出修改。本节我们在Day 5显示和隐藏的基础上让我们模型可以实现拖拽效果,同时对纯色的立方体实现点击随机换色的功能。
195 2
|
监控 vr&ar Swift
visionOS空间计算实战开发教程Day 5 纹理和材质
本文中我们会通过纹理和材质对这个立方体的六个面分别进行不同的绘制。首先我们将ImmersiveView分拆出来,先新建一个ImmersiveView.swift文件,这是一个视图文件,所以请选择User Interface下的Swift View完成创建,其中的内容待我们编写完ViewModel中的代码后再进行修改。
181 0
|
vr&ar
visionOS空间计算实战开发教程Day 4 初识ImmersiveSpace
沉浸式空间为内容提供了一个无界的区域,可在空间内控制内容的大小和摆放位置。在获取用户的授权后,我们还可以使用开启了沉浸空间的ARKit来将内容集成到周遭环境中。例如,可以使用ARKit场景重建来获取家具的网格(mesh)及其附近的对象,让内容可以与网格进行交互。
212 0
SwiftUI—使用withAnimation制作缩放和渐隐动画
SwiftUI—使用withAnimation制作缩放和渐隐动画
1181 0
SwiftUI—使用withAnimation制作缩放和渐隐动画
SwiftUI—如何给图像视图添加边框、透明度和阴影
SwiftUI—如何给图像视图添加边框、透明度和阴影
957 0
SwiftUI—如何给图像视图添加边框、透明度和阴影
|
iOS开发 UED
如何实现 iOS 短视频跨页面的无痕续播?
在一切皆可视频化的今天,短视频内容作为移动端产品新的促活点,受到了越来越多的重视与投入。盒马在秒播、卡顿率、播放成功率等基础优化之外,在用户使用体验上引入了无痕续播能力,提升用户观看视频内容的延续性。本篇将分享盒马在 iOS 短视频方面的实践干货。
如何实现 iOS 短视频跨页面的无痕续播?
|
17天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
下一篇
开通oss服务