WWDC Quick Look 💓 By SwiftGGTeam
Meet UIKit for spatial computing

Meet UIKit for spatial computing

观看原视频

Highlight

UIKit 应用无需重写即可运行在 visionOS 上:添加新目标、处理不可用的 API、使用语义颜色和 hover 效果适配平台风格,再通过 Ornament 和 RealityKit 将内容扩展到三维空间。

核心内容

第一步:把 iPad 应用搬上 visionOS

你有一个用 UIKit 写的 iPad 应用,想让它在 visionOS 上运行。第一步是在 Xcode 的 General 标签页里添加新的运行目标。

visionOS 的应用图标也很特别。它由三张图片叠加而成,当用户注视图标时会产生动态响应效果。你需要在 Asset Catalog 中添加这套分层图标。

02:01

添加目标后直接编译,可能会遇到报错。visionOS 是一个全新平台,有些 API 不再适用。

处理不可用的 API

visionOS 不支持 iOS 14 之前废弃的 API。此外,一些 API 因平台特性差异被标记为不可用:

  • UIDeviceOrientation:假设设备可以旋转,不适用于 visionOS
  • UIScreen:单屏幕表示的概念在这个平台上不成立
  • UITabBar 的 leadingAccessoryView/trailingAccessoryView:Tab Bar 的布局方向不同

03:22

示例中的像素画应用使用了 UIPencilInteraction,visionOS 不支持 Apple Pencil,这个 API 不可用。解决方案是用条件编译把它排除:

#if !os(xrOS)
// Apple Pencil interaction code
let pencilInteraction = UIPencilInteraction()
pencilInteraction.delegate = self
view.addInteraction(pencilInteraction)
#endif

修复编译错误后,应用就能在 Shared Space 中运行了。玻璃材质背景、hover 效果都是系统默认提供的。

语义颜色与材质:让应用融入平台

应用在 iPad 上用黑色标题文字配白色背景看起来很好,但在 visionOS 的玻璃背景上显得突兀。

语义颜色(semantic colors)在这个平台上尤为重要。UIColor.labelUIColor.secondaryLabel 等系统颜色会自动适配平台、外观和辅助功能设置。UILabel 使用语义颜色时默认获得 vibrancy 效果。

06:58

Hover 效果:让交互可感知

visionOS 上,用户注视一个元素时会产生 hover 效果。系统控件默认带有 hover 效果,你也可以为自定义视图添加。

UIView 新增了 hoverStyle 属性。可以设置为 .highlight.lift 效果,也可以用 UIShape API 自定义形状。设置为 nil 则移除 hover 效果。

10:49

输入方式的变化

visionOS 引入了全新的输入系统:

  • 注视 + 手指捏合释放 = Tap 手势
  • 注视 + 捏合并移动手 = Pan 手势
  • 近距离直接触摸 = 物理触摸
  • 触控板 = 配对后可用
  • 辅助功能 = VoiceOver 和 Switch Control 均支持

平台最多支持两个同时输入(每只手一个)。示例应用原来用四指轻扫删除所有内容,在 visionOS 上改为双指即可。

12:17

详细内容

空间化 UIKit presentations

UIKit 的 presentations 在 visionOS 上获得了空间化风格:

Sheet:推出时把 presenting view controller 向后推并变暗。与 iPad 不同,点击 sheet 外部不会自动关闭。

Alert:顶部放置应用图标的 2D 表示。总是从应该被推后的 view controller 呈现。

Popover:在 visionOS 上不受 scene 边界限制,可以超出应用窗口。但要注意 permittedArrowDirections 的设置。

14:26

代码示例:修复 Popover 箭头方向

import UIKit

extension EditorViewController {

    @objc func showDocumentPopover(sender: UIBarButtonItem) {
        let controller = DocumentInfoViewController(document: pixelDocument)
        controller.modalPresentationStyle = .popover
        if let presentationController = controller.popoverPresentationController {
            presentationController.barButtonItem = sender
            if traitCollection.userInterfaceIdiom == .reality {
                presentationController.permittedArrowDirections = .any
            } else {
                presentationController.permittedArrowDirections = .right
            }
        }
        present(controller, animated: true, completion: nil)
    }

}

关键点:

  • traitCollection.userInterfaceIdiom == .reality 检测是否在 visionOS 上运行
  • visionOS 上 popover 不受 scene 边界限制,使用 .any 让系统选择最佳位置
  • iPad 上保持 .right 限制,避免 popover 超出屏幕

Ornament:把内容放到场景之外

Ornament 是 visionOS 的新概念,允许内容放置在应用 scene 的周围。系统组件也在用:SwiftUI 的 TabView 把 tab bar 放在 leading edge 的 ornament 中,Safari 用 ornament 放置导航栏,Freeform 用 ornament 做底部工具栏。

Ornament 抬升于主内容前方,增加了深度感。它们与 view controller 的生命周期绑定,sheet 呈现时会保持相对位置。

17:15

代码示例:创建 Ornament

import UIKit
import SwiftUI

extension EditorViewController {

    func showEditingControlsOrnament() {
        let ornament = UIHostingOrnament(
            sceneAlignment: .bottom,
            contentAlignment: .center
        ) {
            EditingControlsView(model: controlsViewModel)
                .glassBackgroundEffect()
        }

        self.ornaments = [ornament]
        editorView.style = .edgeToEdge
    }

}

关键点:

  • UIHostingOrnament 托管 SwiftUI 内容,需要 import SwiftUI
  • sceneAlignment: .bottom 把 ornament 放在 scene 底部边缘
  • contentAlignment: .center 让内容居中于边缘
  • .glassBackgroundEffect() 为 ornament 添加玻璃背景
  • ornaments 是 UIViewController 的新属性,接受 ornament 数组
  • ornament 与 view controller 生命周期绑定,移除 view controller 时 ornament 自动消失

代码示例:使用语义颜色

private let titleLabelTextField: UITextField = {
    let textField = UITextField()
    textField.textColor = UIColor.label
    return textField
}()

private let authorLabel: UILabel = {
    let label = UILabel()
    label.textColor = UIColor.secondaryLabel
    return label
}()

关键点:

  • UIColor.label 是主文本颜色,自动适配平台和外观
  • UIColor.secondaryLabel 用于辅助文本,降低视觉层级
  • 使用语义颜色的 UILabel 自动获得 vibrancy 效果
  • 避免硬编码 RGB 值,让应用在不同平台上保持一致外观

代码示例:为文本框添加凹陷外观

textField.borderStyle = .roundedRect

关键点:

  • visionOS 上 .roundedRect 会自动呈现凹陷(recessed)外观
  • 这是系统为该平台设计的默认样式,无需额外配置

代码示例:自定义容器背景样式

class MyViewController: UIViewController {
    override var preferredContainerBackgroundStyle: UIContainerBackgroundStyle {
        return .glass
    }
}

关键点:

  • preferredContainerBackgroundStyle 是 UIViewController 的新属性
  • 可选值:.automatic.glass.hidden
  • UINavigationController 和 UISplitViewController 默认使用玻璃背景

代码示例:自定义 Hover 效果

class CollectionViewCell: UICollectionViewCell {
    init(document: PixelArtDocument) {
        super.init(frame: .zero)
        self.hoverStyle = .init(
            effect: .highlight,
            shape: .roundedRect(cornerRadius: 8.0)
        )
    }
}

关键点:

  • hoverStyle 是 UIView 的新属性
  • effect 可选 .highlight.lift
  • shape 使用 UIShape API,支持圆角矩形等形状
  • 设置为 nil 可移除 hover 效果

代码示例:根据平台调整手势

func fourFingerSwipe() {
    let gesture = UISwipeGestureRecognizer(
        target: self,
        action: #selector(self.deleteAll)
    )
    gesture.direction = .left
    if traitCollection.userInterfaceIdiom == .reality {
        gesture.numberOfTouchesRequired = 2
    } else {
        gesture.numberOfTouchesRequired = 4
    }
    self.view.addGestureRecognizer(gesture)
}

关键点:

  • traitCollection.userInterfaceIdiom == .reality 是检测 visionOS 的推荐方式
  • visionOS 最多支持两个同时输入,所以双指轻扫足够
  • iPad 上保持四指轻扫,避免与其他手势冲突

在 UIKit 中使用 RealityKit

RealityView 是新的 SwiftUI view,用于托管 RealityKit 内容。通过 UIHostingController,你可以在 UIKit 应用中嵌入 RealityView,无需重写整个应用。

21:56

代码示例:在 UIKit 中嵌入 RealityView

import UIKit
import SwiftUI
import RealityKit

extension EditorViewController {

    func showEntityPreview() {
        let entityView = PixelArtEntityView(model: entityViewModel)
        let controller = UIHostingController(rootView: entityView)
        addChild(controller)
        view.addSubview(controller.view)
        controller.didMove(toParent: self)
        prepareEditorInteractions()
    }

}

关键点:

  • UIHostingController 将 SwiftUI view 嵌入 UIKit 视图层级
  • PixelArtEntityView 内部使用 RealityView 渲染 RealityKit 实体
  • 标准的 child view controller 生命周期管理:addChildaddSubviewdidMove(toParent:)
  • 这种方式让现有 UIKit 应用无需重写即可获得 3D 能力

核心启发

  • 把现有 iPad 应用移植到 visionOS

    • 做什么:将已有的 UIKit 应用(如绘图工具、笔记应用、阅读器)添加 visionOS 目标,让它在空间中运行
    • 为什么值得做:UIKit 应用几乎零成本获得全新的运行环境,用户可以在更大的虚拟空间中使用你的应用
    • 怎么开始:在 Xcode 的 General 标签页添加 visionOS 目标,处理编译错误(废弃 API 和平台不兼容 API),使用语义颜色替换硬编码颜色
  • 用 Ornament 重新设计工具栏

    • 做什么:把传统底部工具栏或侧边栏提取为 Ornament,让主内容区域最大化
    • 为什么值得做:Ornament 突破了 2D 窗口的边界,工具控件悬浮在主内容周围,既不遮挡又能快速访问
    • 怎么开始:用 UIHostingOrnament 包裹 SwiftUI 工具栏视图,设置合适的 sceneAlignmentcontentAlignment,给 ornament 添加 .glassBackgroundEffect()
  • 为应用添加 3D 预览功能

    • 做什么:在 UIKit 应用中嵌入 RealityView,让用户可以从任意角度查看 3D 内容
    • 为什么值得做:不需要重写整个应用,只需一个 UIHostingController 就能在现有界面中注入 3D 能力
    • 怎么开始:创建 SwiftUI view 包含 RealityView,用 UIHostingController 嵌入到 UIKit 的 view controller 中,通过 child view controller 生命周期管理
  • 设计空间化的 Popover 交互

    • 做什么:利用 visionOS 上 popover 不受 scene 边界限制的特性,设计更灵活的上下文菜单
    • 为什么值得做:popover 可以延伸到应用窗口之外,提供更自然的指向性交互
    • 怎么开始:检查 permittedArrowDirections,在 visionOS 上使用 .any 让系统自动选择最佳位置,避免硬编码平台假设

关联 Session

评论

GitHub Issues · utterances