visionOS示例代码:Hello World

2,587 阅读4分钟

Hello World

使用窗口、体积和沉浸式空间向人们介绍地球。

概述

您可以使用 visionOS 场景类型和样式以有趣和引人入胜的方式共享信息。像体积和沉浸式空间这样的功能可以让您将交互式虚拟对象置于人们的环境中,或将人们置于虚拟环境中。

Hello World 应用程序使用这些工具来向人们介绍地球 - 我们所称之为家园的星球。该应用程序展示了地球的倾斜如何产生季节,物体如何在绕地球运行时移动,以及地球在太空中的外观。

该应用程序使用 SwiftUI 来定义其界面,包括 2D 和 3D 元素。为了创建、自定义和管理 3D 模型和效果,它还依赖于 RealityKit 框架和 Reality Composer Pro。

创建应用程序的入口点

Hello World 在启动时构建了它要显示的场景 - 即出现在 WorldApp 结构中的第一个场景 - 使用 WindowGroup

WindowGroup("Hello World", id: "modules") {
    Modules()
        .environment(model)
}
.windowStyle(.plain)

与其他平台(例如 macOS 和 iOS)类似,visionOS 会将窗口组显示为一个熟悉的窗口外观。在 visionOS 中,人们可以调整窗口的大小并将其移动到共享空间中。即使您的应用程序提供了复杂的 3D 体验,窗口也是应用程序的一个很好的起点,因为它可以让人们轻松进入体验。这也是提供指令或控件的好地方。

提示

这个特定的窗口组使用普通的窗口样式,以保持对 visionOS 会自动添加的玻璃背景效果的控制。

使用导航堆栈呈现不同的模块

在观看了一个简短的介绍性动画,显示出“Hello World”文本输入之后,定义了主要场景内容的 Modules 视图呈现了探索世界不同方面的选项。此视图包含了位于 NavigationStack 根部的目录:

NavigationStack(path: $model.navigationPath) {
    TableOfContents()
        .navigationDestination(for: Module.self) { module in
            ModuleDetail(module: module)
                .navigationTitle(module.eyebrow)
        }
}

在 visionOS 中,导航堆栈的行为与其他平台相同。当它首次出现时,堆栈显示其根视图。当有人选择嵌入的 NavigationLink 时,堆栈会绘制一个新视图并在工具栏中显示一个返回按钮。当有人点击返回按钮时,堆栈会恢复先前的视图。

HW-navigation-stack@2x.png

屏幕截图显示了一个浮动在起居室中的 visionOS 窗口的左上角四分之一。在窗口的左上角出现一个显示向左箭头的返回按钮。窗口的标题 A Day in the Life 出现在按钮的右侧,并在垂直居中。窗口的主要部分显示了标题 Planet Earth,并显示了一些文本段落。

上述代码中 navigationDestination(for:destination:) 视图修饰符的尾部闭包在有人激活链接时显示一个视图,该链接基于来自相应链接的初始化器的模块输入:

NavigationLink(value: module) { /* The link's label. */ }

可能的模块值来自自定义的 Module 枚举:

enum Module: String, Identifiable, CaseIterable, Equatable {
    case globe, orbit, solar
    // ...
}

在新场景中显示一个交互式地球

地球模块以一些关于地球的事实在主窗口中开启,旁边是一个支持内容的装饰性平面图像。为了帮助人们更好地理解,该模块还包括一个名为 View Globe 的按钮,点击该按钮可以在一个新窗口中打开一个交互式 3D 地球。

HW-interactive-globe@2x.png

浮动在起居室中的 visionOS 窗口的截图。窗口的左上角出现一个左指向的 Chevron 形状的返回按钮。窗口的标题 A Day in the Life 出现在按钮的右侧,并在垂直居中。窗口的主体部分显示了标题 Planet Earth,以及几段关于地球的文本。

为了能够打开多种场景类型,Hello World 在其信息属性列表文件中包含了 UIApplicationSceneManifest 键。这个键的值是一个字典,其中包含了一个带有值为 true 的 UIApplicationSupportsMultipleScenes 键:

<key>UIApplicationSceneManifest</key> 
<dict> 
    <key>UIApplicationSupportsMultipleScenes</key> 
    <true/> 
    <key>UISceneConfigurations</key> 
    <dict/> 
</dict>

为地球声明一个体积窗口

在设置了密钥之后,应用程序在其 App 声明中使用了第二个 WindowGroup。这个新的窗口组使用 Globe 视图作为其内容:

WindowGroup(id: Module.globe.name) { 
    Globe() .environment(model) 
}
.windowStyle(.volumetric) 
.defaultSize(width: 0.6, height: 0.6, depth: 0.6, in: .meters)

这个窗口组创建了一个具有任意深度的窗口,非常适合在行为类似于透明盒子的有界区域中显示交互式的 3D 模型,因为 Hello World 使用了 volumetric window style 场景修饰符。人们可以像操作其他窗口一样在 Shared Space 中移动这个盒子,而内容会保持固定。defaultSize(width:height:depth:in:) 修饰符在米单位下指定了窗口的大小,包括深度维度。

Globe 视图包含 3D 内容,但仍然只是一个 SwiftUI 视图。它包含两个元素在 ZStack 中:一个子视图绘制地球的模型,另一个提供了一个控制面板,人们可以用来配置模型的外观。

打开和关闭地球仪窗口

地球模块呈现一个 View Globe 按钮,人们可以点击它来显示体积窗口或关闭窗口,这取决于当前的状态。Hello World 通过创建一个带有按钮样式的 Toggle,并将其嵌套在一个自定义的可重用 WindowToggle 视图中来实现这种行为。

HW-toggle-globe@2x.png

一个在起居室中浮动的 visionOS 窗口的左下角截图,出现在太空中的地球仪的右边,被左边的光照亮。窗口包含几段文字和一个高亮显示的 View Globe 按钮。一个控制面板下面浮动着四个圆形按钮。控制面板中的第一个按钮包含太阳图标并且是高亮的。其他按钮,没有一个是高亮的,分别包含了一个大头针和椭圆、圆形箭头以及云和太阳的图标。

private struct WindowToggle: View {
    var title: String 
    var id: String 
    @Binding var isShowing: Bool 
    @Environment(\.openWindow) private var openWindow 
    @Environment(\.dismissWindow) private var dismissWindow 
    var body: some View { 
        Toggle(title, isOn: $isShowing) 
            .onChange(of: isShowing) { wasShowing, isShowing in 
                if isShowing { 
                    openWindow(id: id) 
                } else { 
                    dismissWindow(id: id) 
                } 
        } 
        .toggleStyle(.button) 
    } 
}

当有人点击开关时,isShowing 状态会发生变化,onChange(of:initial:_:) 修饰符会调用 openWindowdismissWindow 动作来打开或关闭窗口。视图从环境中获取这些动作,并使用与窗口标识符匹配的标识符。

展示绕地球运行的物体

在 visionOS 中,你可以像在其他平台中一样使用窗口。但是,在 visionOS 中,窗口提供了少量的深度,你可以用来创建 3D 效果,例如出现在其他元素前面的元素。Hello World 利用这种深度来在 2D 内容中呈现小型模型。

应用程序的第二个模块,Objects in Orbit,提供关于绕地球运行的物体(如月球和人造卫星)的信息。为了让人们了解这些物体的外观,该模块直接在窗口中显示这些物体的 3D 模型。

HW-3D-models@2x.png

一个以角度查看的 visionOS 窗口的右侧截图。在窗口的左侧,有一个标题和几段文字部分可见,但在图像中大部分被裁剪掉了。窗口的右侧包含一个分段控件和一个 3D 模型。控件上有“Satellite”、“Moon”和“Telescope”的文本,其中第一个被选中。3D 模型带有太阳能电池板和卫星天线,位于窗口表面的前方。

Hello World 使用一个自定义的 ItemView 结构从资源包中加载这些模型,该视图包含一个 Model3D 结构。该视图会对模型进行缩放和定位,以适应可用空间,并应用可选的方向调整:

private struct ItemView: View { 
    var item: Item 
    var orientation: SIMD3<Double> = .zero var body: 
    some View { 
        Model3D(named: item.name, bundle: worldAssetsBundle) { model in 
            model.resizable() 
                .scaledToFit() 
                .rotation3DEffect( 
                    Rotation3D( 
                        eulerAngles: .init(angles: orientation, order: .xyz) 
                    ) 
                 ) 
                 .frame(depth: modelDepth) .offset(z: -modelDepth / 2) 
         } placeholder: { 
             ProgressView() 
                 .offset(z: -modelDepth * 0.75) 
         } 
    } 
}

应用程序会为每个模型使用一次此 ItemView,将每个模型放在一个覆盖层中,该覆盖层仅在基于当前选择时可见。例如,以下覆盖层在 x 轴和 z 轴上稍微倾斜的情况下显示卫星模型:

.overlay { 
    ItemView(item: .satellite, orientation: [0.15, 0, 0.15]) 
        .opacity(selection == .satellite ? 1 : 0)
}

包含这些模型的 VStack 还包含一个 Picker,人们可以使用它来选择要查看的模型:

Picker("Satellite", selection: $selection) { 
    ForEach(Item.allCases) { item in 
        Text(item.name)
    } 
} .pickerStyle(.segmented)
在向 2D 窗口添加 3D 效果时,请记住以下几点指导:
  • 不要过度使用。这些类型的效果增加了趣味性,但可能会在人们从不同方向查看窗口时无意中遮挡重要的控件或信息。

  • 确保元素不超过可用的深度。过度的深度会导致元素被剪切。考虑在初始放置后可能发生的任何位置或方向变化。

  • 避免模型与背景玻璃相交。同样,在初始放置后考虑潜在的移动。

在沉浸式空间中展示地球与其卫星的关系

人们可以通过应用程序的轨道模块来可视化卫星绕地球运动,该模块将地球、月球和通信卫星一起显示为一个单一的系统。人们可以将这个系统移动到环境中的任何位置,或者使用标准手势调整其大小。他们还可以在系统周围移动自己,以获得不同的视角。

一个在起居室中浮动的 visionOS 窗口的左下角截图,出现在太空中的地球-月球系统的右边,被右边的光照亮。窗口包含几段文字和一个高亮显示的 View Orbits 按钮。一个细长的轨迹环绕着地球,从底部开始,然后在地球的上方形成一个圆圈。

注:

要了解在 visionOS 中设计手势,请阅读人机界面指南中的手势部分。

为了创建这个可视化效果,应用程序在一个混合沉浸式样式的 ImmersiveSpace 场景中显示 Orbit 视图,其中包含一个用于建模整个系统的单一 RealityView:

ImmersiveSpace(id: Module.orbit.name) { 
    Orbit() .environment(model) 
} 
.immersionStyle(selection: $orbitImmersionStyle, in: .mixed)

与 visionOS 应用程序中的任何次要场景一样,此场景依赖于在信息属性列表文件中拥有 UIApplicationSupportsMultipleScenes 键。该应用程序还使用了类似于用于窗口的通用切换视图来打开和关闭空间:

private struct SpaceToggle: View { 
    var title: String 
    var id: String 
    @Binding var isShowing: Bool
    @Environment(\.openImmersiveSpace) private var openImmersiveSpace 
    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace 
    var body: some View { 
        Toggle(title, isOn: $isShowing) 
            .onChange(of: isShowing) { wasShowing, isShowing in 
                Task { 
                    if isShowing { await openImmersiveSpace(id: id) 
                } else { 
                    await dismissImmersiveSpace() 
                } 
            } 
        } 
        .toggleStyle(.button) 
    } 
}

与在“打开和关闭地球窗口”部分中出现的窗口等效切换相比,有一些关键的区别:

  • SpaceToggle 使用了环境中的 openImmersiveSpace 和 dismissImmersiveSpace,而不是窗口的等效项。

  • 在这种情况下,dismiss 操作不需要标识符,因为人们一次只能打开一个空间,即使跨应用程序也是如此。

  • 空间的打开和关闭操作是异步的,因此它们出现在一个 Task 内。

从空间中以全沉浸的方式查看太阳系

应用程序的最后一个模块让人们感受地球在太阳系中的位置。与其他模块一样,这个模块包括信息和装饰性图像,以及一个按钮,导向另一个可视化效果,让人们能够从太空中体验地球。

当用户点击按钮时,应用程序会接管整个显示屏,并在所有方向上显示星星,您可以在右侧的视频中看到。地球直接在前方,月球在右侧,太阳在左侧。主窗口还显示了一个小型控制面板,人们可以用它来退出完全沉浸式体验。

HW-solar-system@2x.png

截图显示了地球的一小部分位于星空之中,右侧可见一个窗口。云朵出现在被照亮的部分,而地面的光线则在黑暗的部分可见。窗口的标题为“太阳系”,标题两侧分别有向左和向右的箭头,分别表示后退和前进。后退按钮被调暗。标题下方有一句话,标题下方还有一个标题为“退出太阳系”的按钮。

提示

人们可以通过按下设备的 Digital Crown 来退出全沉浸模式,但通常情况下,您会为您的应用程序提供内置的机制来保持对体验的控制。

该应用程序在此模块中还使用了另一个沉浸式空间场景,但此场景使用了关闭透视视频的全沉浸风格:

ImmersiveSpace(id: Module.solar.name) { 
    SolarSystem() .environment(model)
} 
.immersionStyle(selection: $solarImmersionStyle, in: .full)

这个场景依赖于其他次要场景所使用的相同的 UIApplicationSupportsMultipleScenes 键,并且通过前面描述的自定义 SpaceToggle 激活,但在这种情况下使用了 Module.solar.id 场景标识符。

为了在太阳系控件中重用主窗口,Hello World 将导航栈和控件都放置在一个 ZStack 中,然后设置每个控件的不透明度,以确保每次只显示一个:

ZStack { 
    SolarSystemControls() 
        .opacity(model.isShowingSolar ? 1 : 0) 
    NavigationStack(path: $model.navigationPath) { 
        // ... 
    } 
    .opacity(model.isShowingSolar ? 0 : 1) 
} .animation(.default, value: model.isShowingSolar)