深化并拓展你的visionOS应用,并探索如何将你的应用内容整合到用户的环境中。
概述
配备立体显示的设备让人们以更真实的方式体验3D内容。内容呈现出真实的深度,人们可以从不同的角度观看,使其看起来就像在他们面前一样。
当为visionOS构建应用时,思考你可能会如何增加应用界面的深度。系统提供了多种显示3D内容的方式,包括在你现有的窗口中、在体积中和在沉浸式空间中。选择最适合你的应用和你提供的内容的选项。
窗口
空间容器元素(volume)
沉浸式空间(immersive space)
在传统的2D窗口中添加深度
窗口是应用界面的重要部分。对于visionOS,应用自动获得具有visionOS外观和感觉的材质,完全可调整大小的窗口,以及用于你的自定义控制的高亮度调整。
在需要的时候,将深度效果整合到你的自定义视图中,并使用3D布局选项来在你的窗口中排列视图。
- 应用shadow(color:radius:x:y:)或visualEffect(_:)修改器到视图上。
- 当有人看向视图时,使用hoverEffect(_:isEnabled:)修改器来提升或突出视图。
- 使用ZStack来布局视图。
- 使用transform3DEffect(_:)来动画化与视图相关的改变。
- 使用rotation3DEffect(_:axis:anchor:anchorZ:perspective:)修改器来旋转视图。
除了给2D视图增加更多的深度外,你还可以将静态3D模型添加到你的2D窗口中。Model3D视图加载USDZ文件或其他资产类型,并在你的窗口中以其内在大小显示。在你已经在应用中有模型数据,或可以从网络上下载的地方使用这种视图。例如,购物应用可能使用这种视图来显示产品的3D版本。
使用RealityKit展示动态3D场景
RealityKit是苹果用于构建你可以在屏幕上动态更新的3D模型和场景的技术。在visionOS中,使用RealityKit和SwiftUI一起将你的应用的2D和3D内容无缝地结合在一起。加载现有的USDZ资产,或者在Reality Composer Pro中创建包含动画、物理、光照、声音和你的内容的自定义行为的场景。要在你的应用中使用Reality Composer Pro项目,将Swift包添加到你的Xcode项目中,并在你的Swift文件中导入其模块。有关详细信息,请参阅在你的Xcode项目中管理文件和文件夹。
当你准备在你的界面中展示3D内容时,使用RealityView。这个SwiftUI视图作为你的RealityKit内容的容器,并允许你使用熟悉的SwiftUI技术更新那些内容。
以下示例展示了一个使用RealityView显示3D球体的视图。视图闭包中的代码为球体创建一个RealityKit实体,将纹理应用到球体的表面,并将球体添加到视图的内容中。
struct SphereView: View {
var body: some View {
RealityView { content in
let model = ModelEntity(
mesh: .generateSphere(radius: 0.1),
materials: [SimpleMaterial(color: .white, isMetallic: true)])
content.add(model)
}
}
}
当SwiftUI显示你的RealityView时,它只执行一次你的代码以创建实体和其他内容。因为创建实体相对昂贵,所以视图只运行一次你的创建代码。当你想要更新你的实体的状态时,改变你的视图的状态,并使用一个更新闭包来将这些改变应用到你的内容中。下面的示例使用一个更新闭包来改变球体的大小,当scale属性的值改变时:
struct SphereView: View {
@State var scale = false
var body: some View {
RealityView { content in
let model = ModelEntity(
mesh: .generateSphere(radius: 0.1),
materials: [SimpleMaterial(color: .white, isMetallic: true)])
content.add(model)
} update: { content in
if let model = content.entities.first {
model.transform.scale = scale ? [1.2, 1.2, 1.2] : [1.0, 1.0, 1.0]
}
}
}
}
关于如何使用RealityKit创建内容,请参见RealityKit。
响应与RealityKit内容的交互
要处理与你的RealityKit场景的实体的交互:
将手势识别器附加到你的RealityView并添加targetedToAnyEntity()修改器到它。
将InputTargetComponent附加到实体或其父实体之一。
添加碰撞形状到支持交互的RealityKit实体。
targetedToAnyEntity()修改器为手势识别器和你的RealityKit内容提供了一个桥梁。例如,要识别当有人拖动实体时,指定一个DragGesture。
然后将onChanged(_:) 和onEnded(_:) 修改器附加到手势以响应交互。下面的示例为RealityView添加一个拖动手势,它改变了offset状态的值:
struct DraggableView: View {
@GestureState var offset: SIMD3<Float> = [0, 0, 0]
var body: some View {
RealityView { content in
let model = ModelEntity(
mesh: .generateSphere(radius: 0.1),
materials: [SimpleMaterial(color: .white, isMetallic: true)])
model.components[InputTargetComponent.self] = InputTargetComponent()
content.add(model)
} update: { content in
if let model = content.entities.first {
model.transform.translation += offset
}
}
.gesture(
DragGesture()
.updating($offset, body: { (value, state, _) in
state = [Float(value.translation.width), -Float(value.translation.height), 0]
})
.onEnded({ _ in
self.offset = [0, 0, 0]
})
)
}
}
在这个例子中,DragGesture的updating(_:, body:) 修改器在拖动手势改变时更新offset状态。当拖动手势结束时,onEnded(_:) 修改器重置offset状态为原始值。
为支持交互的RealityKit实体添加碰撞形状。碰撞形状定义了实体的物理形状,并允许RealityKit正确地处理与实体的交互。在创建你的实体时,使用generateCollisionShapes(recursive:) 函数来自动为你的实体和其所有子实体生成碰撞形状。
请注意,你可以使用SwiftUI的 @GestureState属性来跟踪手势的状态,然后在你的update闭包中使用这个状态来更新你的RealityKit实体。
为了更深入地了解如何在你的应用中使用RealityKit,可以参阅RealityKit文档。
在空间容器元素中显示3D内容
空间容器元素是一种窗口类型,其在三个维度上增长以匹配其所包含的内容大小。窗口和空间容器元素都可以容纳2D和3D内容,并且在很多方面都相似。然而,窗口会裁剪超出窗口表面的3D内容,所以对于主要是3D的内容,空间容器元素是更好的选择。
创建空间容器元素,需要在你的应用中添加一个WindowGroup场景,并将其样式设置为空间容器元素样式。这种样式告诉SwiftUI创建一个用于3D内容的窗口。在你的空间容器元素中包含任何你想要的2D或3D视图。你也可以添加一个RealityView来使用RealityKit构建你的内容。下面的示例创建了一个空间容器元素,其中包含存储在应用程序包中的一些气球的静态3D模型:
struct MyApp: App {
var body: some Scene {
WindowGroup {
Model3D("balloons")
}.windowStyle(style: .volumetric)
}
}
窗口和空间容器元素是显示有界2D和3D内容的便捷方式,但是你的应用并不能控制那些内容在人的环境中的位置。系统在显示时间设置每个窗口和体积的初始位置。系统还添加了一个窗口条,以便人们重新定位窗口或调整其大小。
对于何时使用体积的更多信息,请参见人机接口指南 > 窗口。
在人的环境中显示3D内容
当你需要对你的应用内容的位置有更多的控制时,将那些内容添加到一个沉浸式空间。沉浸式空间提供了一个无边界的区域用于你的内容,并且你可以控制空间内内容的大小和位置。在得到用户的许可后,你还可以使用ARKit与沉浸式空间一起,将内容融入他们的环境。例如,你可以使用ARKit场景重建获取家具和附近物体的网格,并让你的内容与该网格进行交互。
沉浸式空间是你创建的与应用的其他场景相同的场景类型。以下示例显示了一个应用,该应用包含一个沉浸式空间和一个窗口:
@main
struct MyImmersiveApp: App {
var body: some Scene {
WindowGroup() {
ContentView()
}
ImmersiveSpace(id: "solarSystem") {
SolarSystemView()
}
}
}
如果你不在你的ImmersiveSpace声明中添加一个样式修饰符,系统会使用混合样式创建该空间。这种样式将你的内容与显示人的环境的透视内容一起显示。其他样式让你可以在不同程度上隐藏透视内容。使用immersionStyle(selection:in:) 修饰符来指定你的空间支持的样式。如果你指定了多种样式,可以使用修饰符的选择参数在样式之间切换。
警告:
请注意你在使用混合样式的沉浸式场景中包含的内容有多少。即使那些内容部分透明,也能填满屏幕的一大部分,这可能会阻止人看到他们环境中的潜在危险。如果你想让人沉浸在你的内容中,使用完全样式配置你的空间。更多信息,请参见,创建应用中的全沉浸式体验。
记得设置你在ImmersiveSpace中放置的项目的位置。使用修饰符设置SwiftUI视图的位置,并使用其变换组件设置RealityKit实体的位置。SwiftUI最初在人的脚下设置空间的原点,但可以响应其他事件更改此原点。例如,系统可能会移动原点以适应使用空间化角色显示你的内容的SharePlay活动。如果你需要将SwiftUI视图和RealityKit实体相对于彼此定位,使用RealityView内容参数中的方法进行所需的坐标转换。
要显示你的ImmersiveSpace场景,使用从SwiftUI环境获取的openImmersiveSpace动作来打开它。此动作异步运行,并使用提供的信息查找并初始化你的场景。以下示例显示了一个打开带有solarSystem标识符的空间的按钮:
Button("Show Solar System") {
Task {
let result = await openImmersiveSpace(id: "solarSystem")
if case .error = result {
print("An error occurred")
}
}
}
当应用展示ImmersiveSpace时,系统会隐藏其他应用的内容以防止视觉冲突。当你的空间可见时,其他应用保持隐藏,但当你关闭它时,它们会返回。如果你的应用定义了多个空间,你必须先关闭当前可见的空间,然后才能显示不同的空间。如果你没有关闭可见的空间,当你试图打开其他空间时,系统会发出运行时警告。