大多数浏览器和
Developer App 均支持流媒体播放。
-
认识用于空间计算的 UIKit
了解如何将开发者的 UIKit App 移植到 visionOS。学习如何为新平台打造 App,探索 API,以及用于空间计算的理想实践。学习在 visionOS 中使用 SwiftUI 与 UIKit,将开发内容带入三维空间。
章节
- 1:35 - Getting started
- 2:56 - Platform differences
- 5:36 - Polishing your app
- 5:52 - Polish: Colors
- 10:20 - Polish: Hover
- 12:12 - Polish: Input
- 13:47 - Outside the bounds
- 15:03 - Outside: Presentations
- 17:11 - Outside: Ornaments
- 21:51 - Outside: RealityKit
资源
相关视频
WWDC23
-
下载
♪ 悦耳的器乐嘻哈 ♪ ♪ Grace Kendall:大家好,欢迎收看 “认识用于空间计算的 UIKit” 我是 Grace Kendall, 是 UIKit 团队的 一名工程师。 我和我的同事 Andrew 很高兴能在本视频中 向你介绍如何将 你现有的 UIKit App 带入一个全新的维度。 新设备拥有令人惊叹的 空间体验平台, 并引入了大量新的 3D 功能。 最棒的是,它使用了你已经熟悉 和喜爱的 UI 框架 来实现这些新功能。 本视频完全侧重于 UIKit 方面的内容。 要进一步了解平台上的 SwiftUI 的内容, 请查看 “认识用于空间计算的 SwiftUI” 和“为空间计算提升你的窗口化 App” 在本视频中,我们将首先介绍 如何为新平台开发 App 以及在这个过程中要采取的步骤。 然后,我们将讨论一些 新平台不同于 其他 Apple 平台的 独一无二的特征, 以及要如何在代码中处理这些不同。 之后,我们将介绍 如何用 UIKit 中 引入的新 API 来完善你的 App。 最后,我们将探索 在 UIKit 之外 使用 SwiftUI 来增加一些 全新的 3D 功能。 那么我们开始吧。 这是一个为 iOS 编写的 像素艺术动画示例 App, 完全由 UIKit 制作。 我的一些好朋友为它贡献了画作。 它在 iPad 上 看起来感觉不错, 利用了大量的系统组件 和动态动画,并且 支持 Apple Pencil。 现在我们要把它 移植到空间计算平台。 我需要做的第一件事 是转到 Xcode 项目中的 “General”标签页, 并添加新的运行目的平台。 现在,在资源目录这里, 我可以添加新的 App 图标。 这是因为新平台上的图标有些特殊。 它们是三个重叠在一起的图像, 当用户看向 App 图标时, 它们会进行动态突出显示。 我们的同事 Jessica 是一位非常有才华的艺术家, 为了这次演示,她为这个 App 制作了全新图标, 下面让我来添加这些文件。
接下来, 我将选择新的设备模拟器作为目标 然后构建。
好吧,有一些构建错误。 看起来有一些用于 iPad App 的 API 在此平台上不可用。 让我们来谈谈原因是什么。 这是一个全新的平台, 具有全新的功能和特性, 这让它 与其他的 Apple 平台不同。 因此你将 App 引入新平台时 需注意一些事项。 当首次将 App 移植到新平台时, 你需要排查 两种常见的 API, 那就是在以前的 iOS 版本中 被弃用的 API 以及不能很好地 转换到新平台的 API。 新设备不能支持 在 iOS 14 之前 弃用的 API。 有了这个全新的平台, 现在正是一个 摆脱被弃用的 API 的好机会, 你可以更新你的共享代码库, 来使用最新最好的代码。 还有一系列的 API 无法转换到新平台。 比如, UIDeviceOrientation。 这个 API 默认设备可以 在多个方向上使用, 但此处并不适用。 同样的,还有 UIScreen。 对于这个设备, 硬件屏幕的单一表示的 前提并不成立。 而且标签页栏的设计有很大的不同, 不再只是从前到后, 因此它的 leadingAccessoryView 和 trailingAccessoryViews 也不可用。 这些只是因为不适用 而被标记为不可用 API 的 三个示例, 但还有其他不可用 API。 请查看文档 获取更多详细信息。 让我们来检查一下代码 看看问题出在什么地方。 让我们看看这里的错误。 在 EditorViewController 扩展中, 有一个 UIPencilInteraction。 新设备不支持 Apple Pencil, 所以这个 API 在这个平台上 不可用。 我可以轻松地将这个代码条件化, 使其编译。 让我们来构建,然后再跑一下程序。
好了,看起来已经启动运行了! 看起来真的很不错 看到 App 在共享空间运行 真的很令人兴奋。 但我已经注意到一些地方, 我认为还可以改进一下。 现在让我们全方位地 浏览一遍 App 找到一些需要改进的地方。 在模拟器中,单击鼠标 就相当于用户看着一个地方 并轻捏手指。 我首先注意到的就是 App 拥有一个漂亮的 玻璃背景。 而当我注视这些图像时 它们会出现很棒的悬停视觉效果 让 App 看起来 生动而有互动性。 搜索栏也有了新外观 看起来是内陷的。
我仍然可以打开不同的图像 随心所欲地进行像素画创作。 好的,我认为我首先要改进的 就是每个图像下面标题的黑色字体 和副标题的灰色字体。 黑色的文字在 iPad 上的 白色背景上看起来非常棒, 但我不喜欢它在玻璃背景上的效果。 我认为它可以更突出一点。 我也非常喜欢搜索栏的外观和尺寸。 我想让标题文本字段 也有这样的效果。 语义化色彩命名并不新鲜, 但是它在这个平台上格外有用。 使用语义化的颜色、字体 和材质非常重要,这样 App 就能 适应平台、外观 和辅助功能设置。 很多东西, 比如 UIColor.label 都有新的值, 这样一切看起来都是像素级的完美。 语义化色彩命名可以适应你的平台, 因此,不要使用 RGB 值 自己定义颜色, 而是使用系统提供的颜色, 这样不管在哪个平台上运行, 你的 App 都可以表现出正确的外观。 例如,systemCyan 在 iOS、macOS 和 watchOS 中 是一种深浅略有不同的蓝色。 在 iPad 上, 这有一个额外的好处, 即它能在浅色和深色外观之间 进行调整。 在这个平台上, systemBackgroundColor 在后面是玻璃背景时 会默认变为透光效果。 同样,使用语义化字体样式, 如标题和正文, 而不是设置静态字体大小, 将使 App 更具可读性。 这样做也有利于辅助功能。 语义化字体的大小 会随着动态类型一起缩放, 以确保可读性。 在这里,TextField 文本 和副标题标签的颜色使用的是 RGB 定义的 UIColor。 与其使用这种方法, 不如在此处使用主标题和次标题的 语义颜色, 这样能更好地适应这个平台, 同时在 iPad 上看起来 仍然很棒。 此外,默认情况下, 所有使用语义颜色的 UILabel 都会变成虚化效果。 我还可以将文本框的边框样式 设置为 roundedRect。 这样将会给视图添加内陷的效果。 让我们构建,再跑一下程序。 这样看起来好多了! 所有标题都很容易阅读,而且很明显 我可以通过使用文本框 来编辑图像的标题。 接下来我们来谈谈材质。 材质是这个平台的一个重要基石。 它们可以使 App 看起来 非常漂亮, 并且让它看起来像是 周围环境的一部分。 它们还能确保 App 在任何环境下都清晰易读。 材质会根据照明条件 和背后物体的颜色 来调整其对比度和色彩平衡。 正因为如此,在这个平台上 没有深色外观和浅色外观之分。 所有的内置控件和容器 也都默认使用透光的材质, 使你的 App 看起来非常漂亮。 现在 App 后面的玻璃背景 看起来非常棒。 这是每个 UINavigationController 和 UISplitViewController 的 默认设置。 这样可以使用户周围的环境 渗透进背景。 你也可以在 UIViewController 上 覆盖新的 preferredContainer BackgroundStyle 属性, 以返回 .automatic .glass 或 .hidden。 接下来,如果我将鼠标悬停在 App 中的一些视图上, 它们会有微弱的高亮显示。 这样能很大程度地 让 App 的互动性变强。 使用控件或列表等系统组件 可以使你获得这些默认效果, 如悬停效果、材质、虚化效果等等。 悬停效果可以增加交互性。 给视图添加悬停效果 可以更好地帮助用户定位。 关于这个平台有一点很重要, 那就是用户正在看的具体位置 是绝对不会 传递给 App 的进程的。 你可以通过 UIKit 中的 全新 API 来添加、自定义 或禁用悬停效果。 UIView 中有一个新属性, HoverStyle。 许多交互式组件 都默认具有悬停效果, 比如各种控件。 你可以通过设置 hoverStyle 属性 来定制视图的悬停效果, 可以选择高亮显示或是上浮。 你也可以将该属性设置为无 来关闭悬停效果。 如果想更进一步,你还可以使用 新的 UIShape API 来提交想要的悬停效果的形状。 我的集合视图单元格上 已经有了一个不错的悬停效果, 但我更希望它有圆角。 要把它的悬停形状改为 圆角的矩形, 我需要设置单元格的 hoverStyle 属性, 并输入一个圆角矩形形状。 这样一来,这个单元格的 矩形悬停效果就从直角 变成了圆角。 我觉得现在它看起来和文本字段 以及背景的圆角非常般配。 现在,当我再看向 每个集合视图单元格时 它们的悬停效果都会是圆角的。
最后提及一点, 那就是输入。 这个平台推出了一种全新的 与内容交互的输入系统。 当用户看向某一元素 捏合然后松开手指 就相当于进行了 一个 TapGesture。 捏住手指并移动手,最后松开, 就相当于 PanGesture。 如果距离 App 足够近, 也可以直接伸出手触摸它。 如果你连接了触控板, 还可以通过它与系统进行交互。 Apple 的辅助功能技术 在设备上同样可用。 旁白和切换控制 可以让所有人使用你的 App。 系统手势识别器适用于 以上所有输入方式,包括触控板。 但是需要注意的是,这个平台上 最多只能同时有两种输入, 因为每只手只能 产生一个不同的触摸。 这个 App 的 iPad 版本 有一个自定义手势 是用四根手指轻扫 来清空画布以重新开始。 我想保留该手势, 所以我不会把它删除。 相反的,我可以修改这个代码 用来检查用户界面的习惯用法, 在现实中, 我可以把所需的触控数量改为两个。 现在我对我的 App 进行了许多改进, 我认为是时候把这个示例 App 提升到新的水平了。 接下来让我的同事 Andrew 来介绍更多内容。 Andrew:谢谢你,Grace! 大家好,我是 Andrew, 是 UIKit 团队的一名工程师。 是时候让你的 UIKit 框架的 App 走出 2D 边界 进入3D世界了。 在 Grace 进行了更新后, 示例 App 看起来非常出色。 但是它和你已经拥有的 UIKit App 都可以利用新的 API 更上一层楼。 我将介绍三种简单的方式 来更新你的 App 以创造出色的空间体验。 首先,UIKit 呈现方式有了 令人兴奋的新空间风格, 可以为你的视图控制器过渡 增加深度。 其次,有一个新的 API 可以将你的内容 带到之前从未到过的地方, 那就是场景之外! 我们称其为装饰。 最后,还有一些强大的新方法可以将 RealityKit 的内容 直接添加到 App 中。 让我们看看如何使用每种方法 将示例 App 提升到新的水平。 你所熟悉和喜爱的 UIKit 呈现方式 即将迎来它在空间计算的首秀。 在 iPad 上,我们的示例 App 使用了表单、提醒和弹出窗口。 让我们看看这些视图 在新平台上表现如何。 首先让我们打开 App 的设置。 具有空间感的表单 将呈现视图控制器推到后方 然后使其变暗。 与 iPad 不同, 它不会因为边界外的 触摸或者其他手势而关闭, 无论视图控制器的 isModalInPresentation 属性如何。 接下来,我们看看新的提醒样式。 一个 2D 的 App 图标视图 直接呈现在最上层。 就像表单一样, 你应始终从视图控制器中 呈现提醒窗口, 并将控制器推到后方。 最后,让我们打开文档信息弹窗。 哦,这个看起来有点不对劲。 它直接超出了编辑器的边界, 这很棒, 但是它离 App 的中心太远了。 让我们来调整一下代码。 看起来视图控制器已经生成了, 然后设定了弹出样式…… 啊,我知道怎么回事了。 permittedArrowDirections 这一项 被设置为仅朝右边。 在 iPad 上,弹出式窗口 会受到场景的限制, 但在空间平台上 这种限制并不存在, 同样,在 macOS 也不存在。 让我们把它更新为总是使用 系统首选的位置。
现在当我点击信息按钮时, 弹出窗口的位置 变得和我预想的一样了。 如果你在 iPad App 中 使用的 是标准呈现方式, 你的 App 在空间平台 可能已经有了可以突破边界的 漂亮视窗。 只要你没有硬编码 任何平台预设,UIKit 将能处理所有细节。 接下来我要用来优 化示例 App 的工具 就是装饰。 虽然现在 App 的呈现方式 已经空间化, 但编辑器本身还并没有利用 平台上的额外空间。 这个编辑器…… 这个吧,看起来还是有点拥挤。 但是利用装饰, 我们可以以前所未有的方式 利用空间平台为我们提供的 额外空间。 装饰可以把内容放置在 App 场景的周围, 但要在合理的范围内。 UIKit 组件也使用装饰, 比如弹出窗口。 所以刚刚示例 App 的弹出窗口 没有被剪切,直接出现在了场景外。 设备上的许多内置 App 都会使用装饰。 这是头戴设备中 TV 的酷炫外观。 这个 App 使用了 SwiftUI 标签页视图, 它将标签页栏放置在场景外 左侧边缘的装饰中。 Safari 浏览器则使用装饰 将导航栏 放在了网页界面的上方。 而无边记使用装饰 创建了其底部工具栏。 利用装饰,这些 App 可以将其主要内容 保持在中间,而把控件推到边缘。 而且装饰会向上浮起, 增加了界面的深度。 它们在所有方向上都突破了边界! 对于示例 App 来说,我认为 将所有编辑控件放入一个装饰工具栏 会有很棒的效果。 让我们回到 Xcode 中 来添加它。 装饰承载 SwiftUI 的内容, 所以我要确保 已经导入了 SwiftUI, 而我已经提前在文件中导入了。 接下来,我要定义新的装饰。 对齐参数可以轻松地表达最适合 你需要的内容布局。 比方说,如果我想要装饰 悬挂在我的场景前端边缘。 我会设置一个前端的场景对齐 和一个后端的内容对齐。 如果我想让装饰悬浮在在场景内部, 我会设置一个前端的内容对齐。 对于编辑工具栏, 我想让它悬浮在场景的底部, 但同时保持工具居中对齐, 这样仍然能感觉 它是编辑器的一部分。 这意味着我需要一个底部场景对齐 和一个中心内容对齐。 接下来我要添加这些对齐参数。 对于内容, 我之前已经制作了一个新的 EditingControlsView, 我要在这里使用它。 装饰不会自动添加背景, 因为你应该根据内容 来选择最合适的背景。 我想要工具栏和我的编辑器有一样的 玻璃背景, 所以我会使用新的修饰符。 接下来,我将把 UIViewController 上 新的装饰的属性 设置为一个只包含新装饰的数组。 如果我有更多的装饰, 我也会把它们加进这个数组。 装饰与其视图控制器共享生命周期。 如果我把一个视图控制器 从层次结构中移除, 其包含的装饰也会被移除。 这种关联对于系统交互至关重要。 比如,在转换过程中, 表单视图的演示 将保持装饰与其视图控制器相对应。 最后,要注意避免 发生意外重叠的情况。 我要对代码进行最后一项修改。 由于我已经将控件移到了装饰中, 我现在有更多空间供主编辑器使用。 我将用我的自定义 edgeToEdge 样式 使它变得更大一些。 是时候运行 App 了。
太棒了! 工具栏正好在我想要的位置, 突破了编辑器的底部边界。 真不错。 通过利用装饰, 示例 App 现在可以 将更多的主要空间 用于展示内容, 这也是创作者最关心的部分, 同时保持编辑工具仍然在附近。 制作一个装饰非常简单。 你可以将时间和精力集中在 让自己的 App 变得更独特上。 最后,我们来谈谈如何将 RealityKit 添加到 UIKit App 中。 有一个新的 SwiftUI 视图, RealityView, 用来承载 RealityKit 内容。 这使得实体可以在 SwiftUI 层次结构中 作为父级。 如果要深入了解 RealityView, 请一定要观看 “利用 RealityKit 打造空间体验” 还有一个现有的 API, UIHostingController, 它可以承载 SwiftUI 的视图。 这意味着你可以利用 RealityView 以及 SwiftUI 中的 其他新 API, 而无需重写 UIKit App。 对于示例 App, 我想使用 RealityKit 让像素变得栩栩如生。 让我们打开 Xcode 来创造一些奇迹。 我已经创建了 一个新的 SwiftUI 视图, 叫做 PixelArtEntityView。 它使用 RealityView 将图像中的像素 渲染成 RealityKit 实体。 我将创建一个新的实例来开始。 然后我将设置 UIHostingController, 使用实体视图作为根视图。 我会将托管控制器 添加为 EditorViewController 的子项, 并将托管控制器的视图添加为 EditorViewController 视图的 子视图。 接下来,我会将托管控制器 移动到新的父级。 最后,我将调用我的自定义布局函数 来定位预览。 好了,托管控制器已经设置完毕了。 接下来好戏开场了。
让我按下播放按钮, 点击 3D 预览, 看看新代码的运行情况。 没错就是这样, 我在 App 中添加了 具有真实深度的像素。 通过模拟器轨道控制, 我甚至可以看到光线是如何 根据我的视角而变化的。 太酷了! 而且这一切都发生在 UIKit App 中。 RealityView 提升了动画预览的水平。 而且多亏了 UIHostingController, 我们添加它也很容易。 所有这些 API 可以很大程度上 帮助我们制作出 优秀的空间 App。 通过使用 标准的 UIKit 呈现方式, 再将编辑器的控制键放入装饰中, 最后再用 RealityKit 添加 3D 像素, 我们让示例 App 在这个新的空间世界大放异彩。 而这一切只需要几行代码。 要了解这些空间体验的 设计指南, 请查看“空间设计原则” 这个视频涵盖了很多内容, 所以你接下来要做的有几件事。 首先,为你的项目添加新的 运行目标平台。 然后根据设备的需求, 更新所需的 API, 并移除已经废弃的 API。 使用语义化的样式、悬停效果 以及标准呈现方式使你的 App 与平台的外观保持一致。 用装饰将你的想法和想象力 延伸至边界之外。 最后,利用 UIKit 提供的 新的空间 SwiftUI 功能, 让你的 App 更上一层楼。 感谢观看! 我和 Grace 非常期待看到 各位的 App 进入全新的维度! ♪
-
-
16:15 - permittedArrowDirections
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) } }
-
19:46 - Ornament
extension EditorViewController { func showEditingControlsOrnament() { let ornament = UIHostingOrnament(sceneAlignment: .bottom, contentAlignment: .center) { EditingControlsView(model: controlsViewModel) .glassBackgroundEffect() } self.ornaments = [ornament] editorView.style = .edgeToEdge } }
-
22:45 - UIHostingController
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() } }
-
22:46 - Using Semantic Colors
private let titleLabelTextField: UITextField = { textField.textColor = UIColor.label return textField }() private let authorLabel: UILabel = { label.textColor = UIColor.secondaryLabel return label }()
-
22:47 - Adding a recessed appearance to a text field
textField.borderStyle = .roundedRect
-
22:48 - Overriding preferredContainerBackgroundStyle
class MyViewController: UIViewController { override var preferredContainerBackgroundStyle: UIContainerBackgroundStyle { return .glass } }
-
22:49 - Customizing hover style
class CollectionViewCell: UICollectionViewCell { init(document: PixelArtDocument) { self.hoverStyle = .init( effect: .highlight, shape: .roundedRect(cornerRadius: 8.0)) } }
-
22:50 - Checking user interface idiom
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) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。