问答:针对 visionOS 构建 App

在过去的几个月里,Apple 专家在世界各地的 Apple Vision Pro 开发者实验室中回答了有关 visionOS 的问题。以下是他们被问到的一些最常见问题的答案,包括对实体、沉浸式空间、碰撞形状等新概念的重要见解。

如何使用手势与实体交互?

实现基于手势的实体交互需要三个重要元素:

  1. 实体必须具有 InputTargetComponent。否则,它完全无法接收手势输入。
  2. 实体必须具有 CollisionComponent。碰撞组件的形状定义了手势实际可以击中的区域,因此请确保正确指定碰撞形状,以便与实体进行交互。
  3. 你使用的手势必须针对你尝试与之交互的实体 (或任何实体)。例如:
private var tapGesture: some Gesture {
    TapGesture()
        .targetedToAnyEntity()
        .onEnded { gestureValue in
            
            let tappedEntity = gestureValue.entity
                
            print(tappedEntity.name)
        }
}

为交互式实体提供一个 HoverEffectComponent 也是不错的主意,这让系统能够在用户查看实体时触发标准的突出显示效果。

我应该使用窗口组、沉浸式空间,还是两者皆用?

当你决定为 App 中的特定功能使用哪种场景类型时,请考虑窗口、空间容器和沉浸式空间之间的技术差异。

以下是一些重要的技术差异,请在做出决定时加以考虑:

  1. 当沉浸式空间打开时,用户打开的其他 App 的窗口和空间容器将被隐藏。
  2. 窗口和空间容器会裁剪超出其范围的内容
  3. 用户可以完全控制窗口和空间容器的放置。App 可以完全控制沉浸式空间中内容的放置。
  4. 空间容器具有固定大小,窗口可调整大小。
  5. ARKit 仅在具有开放的沉浸式空间时才将数据传送到你的 App。

探索 Hello World 示例代码,熟悉 visionOS 中每种场景类型的行为。

如何可视化场景中的碰撞形状?

使用“调试可视化”菜单中的“碰撞形状”调试可视化;你还可以在该菜单中找到其他几个有用的调试可视化。有关调试可视化的信息,请查看诊断运行中的 App 出现的外观问题

可以将 SwiftUI 视图放置在沉浸式空间中吗?

当然可以!你可以使用 offset(x:y:)offset(z:) 方法将 SwiftUI 视图放置在沉浸式空间中。请务必记住,这些偏移量是以点而非米为单位指定的。你可以利用 PhysicalMetric 将米转换为点。

如果我想相对于现实视图中的实体定位 SwiftUI 视图,该怎么办?

使用 RealityView 附件 API 创建 SwiftUI 视图,并使其可作为 ViewAttachmentEntity 进行访问。该实体可以像任何其他实体一样定位、定向和缩放。

RealityView { content, attachments in
            
    // Fetch the attachment entity using the unique identifier.
    let attachmentEntity = attachments.entity(for: "uniqueID")!
            
    // Add the attachment entity as RealityView content.
    content.add(attachmentEntity)
            
} attachments: {
    // Declare a view that attaches to an entity.
    Attachment(id: "uniqueID") {
        Text("My Attachment")
    }
}

可以通过编程来定位窗口吗?

没有 API 可用于定位窗口,但我们很乐意了解你的用例。请提交改进请求。有关本主题的更多信息,请查看定位和调整窗口大小

有没有办法知道用户在查看什么?

采用隐私和用户偏好的最佳实践 中所述,系统在处理摄像头和传感器输入时,不会直接将信息传递给 App。因此,并无方法可获得精确的眼球运动或确切的视线。相反,重点应是创建人们可以交互的界面元素,并让系统管理交互。如果你有一个无法以这种方式运作的用例,并且它不需要明显的眼球运动追踪,请提交改进请求。

visionOS 上何时调用 onHover 和 onContinuousHover 操作?

当手指悬停在视图上,或连接的触控板上的指针悬停在视图上时,将调用 onHover (英文)onContinuousHover 操作 (英文)

可以在 App 中展示我自己的沉浸式环境纹理吗?

如果你的 App 打开了 ImmersiveSpace,则可以使用 UnlitMaterial 创建一个大球体,并将其缩放为内向式的几何体:

struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            do {
                // Create the sphere mesh.
                let mesh = MeshResource.generateSphere(radius: 10)
                
                // Create an UnlitMaterial.
                var material = UnlitMaterial(applyPostProcessToneMap: false)
                
                // Give the UnlitMaterial your equirectangular color texture.
                let textureResource = try await TextureResource(named: "example")
                material.color = .init(tint: .white, texture: .init(textureResource))
                
                // Create the model.
                let entity = ModelEntity(mesh: mesh, materials: [material])
                // Scale the model so that it's mesh faces inward.
                entity.scale.x *= -1
                
                content.add(entity)
            } catch {
                // Handle the error.
            }
        }
    }
}

我已有立体视频,如何将其转换为 MV-HEVC?

AVFoundation 提供了 API,以编写 MV-HEVC 格式的视频。

要将视频转换为 MV-HEVC,请按以下步骤操作:

  • 为左右视图各创建一个 AVAsset

  • 使用 AVOutputSettingsAssistant 获取适用于 MV-HEVC 的输出设置。

  • 指定水平视差调整和视野 (特定于资源)。我们来看一个示例:

var compressionProperties = outputSettings[AVVideoCompressionPropertiesKey] as! [String: Any]
        
        // Specifies the parallax plane.
        compressionProperties[kVTCompressionPropertyKey_HorizontalDisparityAdjustment as String] = horizontalDisparityAdjustment
        
        // Specifies the horizontal FOV (90 degrees is chosen in this case.)
        compressionProperties[kCMFormatDescriptionExtension_HorizontalFieldOfView as String] = horizontalFOV
// Create a tagged buffer for each stereoView.
                        let taggedBuffers: [CMTaggedBuffer] = [
                            .init(tags: [.videoLayerID(0), .stereoView(.leftEye)], pixelBuffer: leftSample.imageBuffer!),
                            .init(tags: [.videoLayerID(1), .stereoView(.rightEye)], pixelBuffer: rightSample.imageBuffer!)
                        ]
                        
                        // Append the tagged buffers to the asset writer input adaptor.
                        let didAppend = adaptor.appendTaggedBuffers(taggedBuffers,
                                                    withPresentationTime: leftSample.presentationTimeStamp)

如何在 visionOS 上的 RealityKit 中照亮场景?

你可以通过以下方式在 visionOS 上的 RealityKit 中照亮场景:

  • 使用系统提供的自动照明环境,可根据真实环境对环境进行更新。

  • 通过 ImageBasedLightComponent 提供自己的基于图像的照明。要查看示例,请创建一个新的 visionOS App,选择 RealityKit 作为沉浸式空间渲染器,然后选择 Full 作为沉浸式空间。

我发现 visionOS 不支持 CustomMaterial。是否有方法可以创建具有自定义着色的材质?

你可以使用 Shader Graph 在 Reality Composer Pro 中创建具有自定义着色的材质。以这种方式创建的材质可以作为 ShaderGraphMaterial 供 App 访问,以便你可以动态更改代码中着色器的输入。

有关 Shader Graph 的详细介绍,请观看探索 Reality Composer Pro 中的材质

如何相对于设备的位置定位实体?

在 ImmersiveSpace 中,你可以使用 queryDeviceAnchor(atTimestamp:) 方法获取设备的完整转换。

进一步了解如何构建 visionOS 的 App

问答:针对 visionOS 进行空间设计

View now

Spotlight on: Developing for visionOS

View now

Spotlight on: Developer tools for visionOS

View now

本文包含的示例代码是根据《Apple 示例代码许可》(英文) 提供的。