大多数浏览器和
Developer App 均支持流媒体播放。
-
构建桌面级 iPad App
了解如何构建可利用桌面级功能的 iPad App。来自 UIKit 团队的 Mohammed 将和您一起探索最新的导航、集合视图、菜单和编辑 API,学习有关如何构建功能强大的 iPad App 的最佳实践。在观看这个讲座的同时实时编写代码,或下载我们的示例 App 帮助您在更新代码时进行参考吧。
资源
- Building a desktop-class iPad app
- centerItemGroups
- collectionView(_:contextMenuConfigurationForItemsAt:point:)
- collectionView(_:performPrimaryActionForItemAt:)
- Supporting desktop-class features in your iPad app
- titleMenuProvider
- UIDocumentProperties
- UINavigationItem.ItemStyle
- UINavigationItemRenameDelegate
相关视频
WWDC23
WWDC22
-
下载
♪ ♪
Mohammed: 大家好 我是 UIKit 的 Mohammed 感谢大家和我一起 深入探索 桌面级 iPad App 的构建 在这个视频中 我们使用 iPadOS 16 API 来将当前 iPad App 更新为 桌面级体验 我们首先使用全新的 导航工具栏 API 展示其强大的功能 增加 UI 密度 提供自定义
然后 我们应用 新的 UICollectionView 和 Menu API 以启用复杂工作流 及多重选择的快速活动栏 最后以启用新的 查找和替换体验结束整个操作 使用新的编辑菜单 增强文本编辑 我们更新的 App 是为 iPadOS 15 构建的 Markdown 编辑器 我们查看现代化过程的 每个步骤时 我会与大家解释该选择背后的 最佳实践方法及动机 以便您在将 App 应用到 类似的流程中时 能知道需要考虑的因素有哪些
如果开始前 您需要一些入门知识 可查看“桌面级 iPad 简介”视频 里面有所有 UIKit 全新 iPadOS API 分类 以及”iPad App 设计的 最新更新”视频 了解更多关于设计最佳的 桌面级 iPad App 的技巧 好了 我们开始吧 在开始前 我们先想一下 我们 App 控制的配置 由于该 App 是为 iPadOS 15 设计的 在导航工具栏中 已经展示了最重要的控制功能 并在不同的菜单和 popover 弹出框中 置入了二级控制功能
在 iPadOS 16 中 UIKit 使当前的 导航样式更正式 并推出了两个全新的样式 布局更紧密 自定义程度更高 这让 App 可以用最适配内容的 布局方式 并为前端 UI 带来更多功能
导航 App 有类似的推送 或弹出的导航模型 这整体来说 对展示 层级数据的 App 是适用的 如设置
诸如 Safari 等浏览器或文件 适用于浏览 及在多个文档或文件夹结构中 往返导航
而编辑器 则对集中查看或编辑 独立文档非常适用
对 Markdown 编辑器来说 这一样式最适配我们的 App
编辑样式的标题在工具栏 前端对齐 中间是项目组 在其它视图或菜单中隐藏的 额外功能 在这个样式中 就能看到 我们尽可能利用这种设计 做更多的操作 首先是自定义内置的返回动作 来响应我们的需求 然后在标题菜单加上文档信息 以及常用的文档操作 我们还添加支持使用新内置 重命名 UI 来重命名文档 最后 我们将之前隐藏的功能 放到工具栏中间 让它们更易于访问 首先 我们将 view controller 的 navigationItem 样式属性 设置为 .editor 以选择编辑样式
这一操作后 标题马上调整为 向前对齐 并留空了中心区域
随后 移除后面的完成按钮 并用新的 backAction API 进行替换 这样样式外观更标准 摒弃了这一视图 并返回文档选择器
接下来 我们看下这一标题菜单 对我们的 App 是否有用 正如它的名字所述 标题菜单正是 显示在导航工具栏的标题视图 这里显示文档元数据 及展示整个文档的操作 是非常合适的 如果您的 App 不是基于文档的 这个地方最适合展示应用 完整视图的操作 对我们的 App 来说 可使用文档菜单的标头 来展示关于该文档的 更多有用信息 我们还可以提供 文档的可拖拽表示 并让共享功能更易访问 现在 该是时候写一些代码了
我们的 App 是 UIDocument 支持的 所以我们可以使用 UIDocument 的 fileURL 来例举 UIDocumentProperties 对象
接下来 我们使用同样的 URL 来创建一个 NSItemProvider
然后我们使用项目提供程序 来创建一个从属性对象的 dragItemsProvider 返回的 UIDragItem
我们还可用其构建一个从属性对象的 activityViewControllerProvider 中 返回的 UIActivityViewController 最后 我们设置属性对象为 编辑 view controller 的 navigationItem 的 documentProperties 我们刚刚写的代码 显示的是这个文档标头 可提供文档快速概览 包括其名称 文件大小和 图标 由于我们指定了拖动项目 和活动 view controller 提供程序 我可以拖动图标 将文档复制到 App 之外 或点击共享按钮 触发 activity view controller
除了展示文档标头 标题菜单这一位置也可用于 提供应用于整个文档的功能 这一菜单中可显示两种操作 预设的本地化标题和字符图像的 系统提供操作 以及 App 提供的自定义操作
由于附带了一些额外功能 我们首先看看重命名操作 我们可以通过遵照重命名 代理协议 将这一操作添加到我们的菜单中 触发后 这一操作会出现在 工具栏的内置重命名 UI 中
首先 我们将 view controller 指定为 其导航项的 renameDelegate
然后 我们执行 navigationItemDidEndRenamingWithTitle 来进行所展示文档 实际重命名操作
这一功能在重命名操作 提交后调用 App 负责重命名文档 处理此操作 有意打开的 API 最后 可支持您 App 可能有的 各种数据模型 接着看另一个系统提供操作 我们首先需要在我们的 编辑 view controller 中重写它们的功能 这里我们应用了复制和移动功能 UIKit自动在 navigationItem 的 titleMenuProvider 中 作为建议 UIMenuElements 的数组 展示系统提供操作 包括重命名操作 要将其置入我们的标题菜单中 只需将它们添加到 返回菜单的 children 中
除了系统指定操作 我们可以添加完整的自定义操作 甚至整个菜单层级 这里我添加了 Export 子菜单 包括导出为 HTML 和 PDF 的子 Action
因此 点击标题视图可调出 包含文档标头 以及新添加的所有操作的菜单 我选择重命名时 可激活内置重命名 UI 我可以重命名文档
现在我们开始建立 App 的 基础结构 我们用 Mac catalyst 搭建 App 时 就可以查看其外观了 当我们在 Mac 上运行 App 时 会发现编辑样式中的 标题向前对齐已经被完美转译
我们的返回动作也可继续 单击后 会调出文件浏览器
系统提供动作和重命名功能 自动显示在 App 的文件菜单中 Mac Catalyst 不会 调用 titleMenuProvider 所以我们的自定义操作 并不包含在文件菜单中 要显示此操作 我们需要使用 主 UIMenuSystem 手动添加到 App 的主菜单中
好了 我们继续现代化进程 我们继续在 Mac 中查看 以逐步达成目标 我们想想工具栏中间区域 提供的可用选项 iOS 15 版本的 App 有一个菜单 可显示许多二级控制功能 和工具 使用中间项目 我们可以让 这些工具更容易被搜索和发现
由于中间区域是可自定义的 我们可以加入一大组控制按钮 而无需担心用不常用按钮 充填 UI 每个人都可以自定义工具栏内容 使其适应自己的工作流 启用自定义的第一步是 在 navigationItem 中 指定一个 customizationIdentifier
接下来 我们将中间项 定义为 UIBarButtonItemGroups Groups 是一个现有概念 扩展到 UINavigationBar 并增强了对 iOS 16 自定义的支持 这一屏幕快照展示了一组 我们想要默认显示的中间项 左边的同步滚动按钮 提供了其它任何方式 都无法企及的重要功能 因此可以用 UIBarButtonItem 新创建的 creatingFixedGroup() 功能 来将其置于固定组中 固定组不可自定义 也不能被用户移动
另一方面 添加链接按钮 不提供关键功能 同样的任务可以通过在编辑器中 输入链接标签来实现 所以我们使用 creatingOptionalGroup 来创建一个完整的自定义项目 我们为其指定一个 独特的 customizationIdentifier 这样在 App 的登陆期间 自定义操作能保持一致
我们用类似的流程在默认组中 定义剩余的项目 然后继续操作无需默认的 低优先级项目 其中之一就是文本格式组 包括粗体 斜体 下划线项目
这些项目不是特别重要 无需在默认中显示 但我们希望能在自定义的 popover 弹出框中 显示 这样能被拖入工具栏中
要实现这一效果 我们可以 使用 UIBarButtonItemGroup 的 optionalGroup 初始化器 isInDefaultCustomization 设置为 false
我们也要为该组指定代表项目 因此在 popover 弹出框中 能显示标题 以及在工具栏空间有限时 能显示缩略的信息
回到 iPad 中 我们定义的中间项 会显示在工具栏的中间区域 如果我点击新增的“更多”按钮 会显示一个带有 “自定义工具栏”的菜单 单击后 自定义模式会被激活
我们标记为固定的同步滚动按钮 不再强调且变为静态 其它所有项目都弹起并抖动 表示它们都是可自定义的
可选项如格式组在 popover 弹出框中显示 可被拖入工具栏中
我们在 Mac 中运行 App 时 会发现中间项 已经被转换为完全自定义的 macOS 工具栏按钮
我们继续下一步前 先暂时 回到 iPad 重新调整 App 的大小 现在 我们工具栏上的 可用空间更少了 中间项已不可见 UIKit 会自动处理中间项 的显示和隐藏 来适应不同的可用空间 无法包含的额外项目都会 在溢出菜单中显示 标准工具栏按钮项将自动转换为 对应的菜单选项 但如果需要的话 我们也可以提供自定义菜单选项 由于 UIKit 对自定义视图项的目的 没有对应方案 我们的滑块项并不会自动转译 我们需要手动指定菜单项目
这是我们的滑块项 这是有自定义视图的 单一工具栏按钮项 隐藏于可选工具栏按钮组之下 为了提供滑块的核心功能 我们将 menuRepresentation 定义为 带有减少 重置 和增加动作的 UIMenu
使用 UIMenu 的 新 preferredElementSize 属性 我们可以为菜单提供 更紧凑的并行动作
使用新的 keepsMenuPresented 属性 我们可以保证每个动作后的 菜单展示都能正常运行 在不取消 或重新显示菜单的情况下 让字体大小 可以多次修改 让我们再次在 iPad 中运行看看 现在我们调出溢出菜单 滑块显示为有三个并行行动的 内联菜单 包含了滑块的全部功能
由于 Mac 中不存在 小元素尺寸 动作将显示为标准 mac OS 菜单项 以上是 UI 配置和自定义 接下来 我们看看如何在 App 中 使用新的集合视图和菜单 API 加速一些工作流程 我们的 App 有一个 侧边栏内容表格 可用于快速导航文档 或操作顶层标签 在 iOS 16 之前 增加编辑多重项目的能力 不一定意味着执行 不同的编辑模式 将批量操作 降低为工具栏中的按钮
iOS 16 推出多项目菜单的 全新设计 有一系列的项目明确指出了 菜单影响是哪个项目 提供了多项目拖拽的直接转换 在桌面级 iPad App 中 这个新的菜单设计与 轻量型选择样式最为匹配 这里的“轻量型” 是指选择多重项目时 无需在编辑模式中开启 集合视图 或对 App 的 UI 做出 重大改变 我们使用当前 API 即可实现 并启动键盘聚焦 首先 我们将 allowsMultipleSelection 设置为 true
然后将 allowsFocus 设置 为 true 启动键盘聚焦
我们可以设置 selectionFollowsFocus 为 true 将聚焦转变为驱动选择
如果在 iPad 上运行 我们马上就能注意到 每个项目都添加到了选择中 但仍然触发了选择动作 导致编辑器视图滚动 我们回到代码 看下是什么原因
找到了 didSelectItemAtIndexPath 中的代码 尝试在编辑模式中 通过检查collectionView 的 isEditing 属性来禁止滚动 现在我们已经允许了编辑模式外的 多重选择 这一代码在每次选择时都会运行 我们可以通过使用新的 UICollectionViewDelegate 方法来修正 我们执行 performPrimaryActionForItemAtIndexPath 将滚动代码移动到这个新功能中 由于这个功能仅在点击 单个项目时才会调用 集合视图并没有编辑 我们不需要检查编辑模式
由于我们没有与选择相关的行为 就可以将 indexPath 中 所做的选择项操作移除
回到 iPad 编辑器视图中 选择多重项目不再滚动到 对应的文本中 完成这一操作后 我们将其真正添加到菜单支持中
在 iPadOS 16 中 UICollectionViewDelegate 当前单一项菜单方法 已不适用 其替代方法支持在任意位置从 0 到多重项目显示菜单 指定 indexPaths 数组中的项目数量 取决于项目是如何选取的 以及菜单是在何处激活的
如果数组为空 则菜单在单元格 之间的空白区激活
如果有单一的 indexPath 则在取消选择 或单一选择项目上激活
如果有超过一个项目 则菜单在 多重选择部分激活
如果我回到 iPad 中 再次选择上面的四个项目 双指单击其中一个选择项目 会出现新的多项菜单
当我在 Mac 中做同样的操作时 在选择单元格周围 会绘制一个圆环以突出强调
多项菜单完成后 我们看看使用新的 查找和替换和编辑菜单功能 增强文本编辑体验 我们的 App 使用 UITextView 作为编辑器 无需任何自定义的 查找和替换行为 因此 我们启用默认系统功能 只需要将文本视图的 isFindInteractionEnabled 属性设置为 true 这一设置完成后 编辑文本时 按 Command+F 调出查找和替换 UI
在文本视图的编辑菜单中增加 自定义行动并不需要多大成本 但却能启动一些强大 快速的 编辑功能 我们只用执行新的 UITextViewDelegate 方法 在范围建议行动内编辑文本菜单 在执行操作中 我们可以构建 和返回结合自定义行动的 与系统菜单的 UIMenu 如这个隐藏行动
结果如下 当我选择文本 调用编辑菜单时 自定义操作和 系统提供操作都会显示 您可观看“采用桌面级 编辑交互”视频 以获取更多关于 查找和替换及编辑菜单的信息 就是这样 有了这些改变 我们就能通过 一些重大的基础步骤 制作桌面级 App 并将其无缝转译到 Mac 中 使用 iPadOS 16 提供的 API 用类似的流程操作您的 App 首先选择适合您 App 的 导航样式 用文档属性和标题菜单 增强文档工作流程 用中间项显示重要功能 提供自定义功能 在多项目菜单中启动 多重项目的快速活动栏 用查找和替换 以及全新的编辑菜单 增强 App 的文本编辑体验 不管您是构建一个新的 App 还是更新现有 App 我都迫不及待想使用 您应用了这些新工具的 App 了 感谢大家的观看
-
-
3:36 - Enable UINavigationBar editor style.
navigationItem.style = .editor
-
3:52 - Set a back action.
navigationItem.backAction = UIAction(…)
-
4:48 - Create a document properties header.
let properties = UIDocumentProperties(url: document.fileURL) if let itemProvider = NSItemProvider(contentsOf: document.fileURL) { properties.dragItemsProvider = { _ in [UIDragItem(itemProvider: itemProvider)] } properties.activityViewControllerProvider = { UIActivityViewController(activityItems: [itemProvider], applicationActivities: nil) } } navigationItem.documentProperties = properties
-
6:36 - Adopt rename title menu action and rename UI
override func viewDidLoad() { navigationItem.renameDelegate = self } func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith title: String) { // Rename document using methods appropriate to the app’s data model }
-
7:09 - Adopt system provided title menu actions.
override func duplicate(_ sender: Any?) { // Duplicate document } override func move(_ sender: Any?) { // Move document } func didOpenDocument() { ... navigationItem.titleMenuProvider = { [unowned self] suggested in var children = suggested ... return UIMenu(children: children) } }
-
7:10 - Add custom title menu actions
func didOpenDocument() { ... navigationItem.titleMenuProvider = { [unowned self] suggested in var children = suggested children += [ UIMenu(title: "Export…", image: UIImage(systemName: "arrow.up.forward.square"), children: [ UIAction(title: "HTML", image: UIImage(systemName: "safari")) { ... }, UIAction(title: "PDF", image: UIImage(systemName: "doc")) { ... } ]) ] return UIMenu(children: children) } }
-
9:35 - Enable customization for center items
navigationItem.customizationIdentifier = "editorView"
-
10:00 - Define a fixed center item group.
UIBarButtonItem(title: "Sync Scrolling", ...).creatingFixedGroup()
-
10:23 - Define an optional (customizable) center item group.
UIBarButtonItem(title: "Add Link", ...).creatingOptionalGroup(customizationIdentifier: "addLink")
-
10:56 - Define a non-default optional center item group.
UIBarButtonItemGroup.optionalGroup(customizationIdentifier: "textFormat", isInDefaultCustomization: false, representativeItem: UIBarButtonItem(title: "Format", ...) items: [ UIBarButtonItem(title: "Bold", ...), UIBarButtonItem(title: "Italics", ...), UIBarButtonItem(title: "Underline", ...), ])
-
13:16 - Define a custom menu representation for a bar button item group.
sliderGroup.menuRepresentation = UIMenu(title: "Text Size", preferredElementSize: .small, children: [ UIAction(title: "Decrease", image: UIImage(systemName: "minus.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, UIAction(title: "Reset", image: UIImage(systemName: "1.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, UIAction(title: "Increase", image: UIImage(systemName: "plus.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, ])
-
15:10 - Enable multiple selection and keyboard focus in a UICollectionView.
// Enable multiple selection collectionView.allowsMultipleSelection = true // Enable keyboard focus collectionView.allowsFocus = true // Allow keyboard focus to drive selection collectionView.selectionFollowsFocus = true
-
16:11 - Add a primary action to UICollectionView items.
func collectionView(_ collectionView: UICollectionView, performPrimaryActionForItemAt indexPath: IndexPath) { // Scroll to the tapped element if let element = dataSource.itemIdentifier(for: indexPath) { delegate?.outline(self, didChoose: element) } }
-
16:56 - Add a multi-item menu to UICollectionView.
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? { if indexPaths.count == 0 { // Construct an empty space menu } else if indexPaths.count == 1 { // Construct a single item menu } else { // Construct a multi-item menu } }
-
18:12 - Enable Find and Replace in UITextView.
textView.isFindInteractionEnabled = true
-
18:34 - Add custom actions to UITextView's edit menu.
func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? { if textView.selectedRange.length > 0 { let customActions = [ UIAction(title: "Hide", ... ) { ... } ] return UIMenu(children: customActions + suggestedActions) } return nil }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。