大多数浏览器和
Developer App 均支持流媒体播放。
-
使您的 iPad App 更上一层楼
制作更加优秀的 iPad App:了解如何采用极佳的场景,确保不间断的重点交互。通过键盘快捷键和键盘快捷键界面帮助人们保持投入和快速操作。探索最新指针增强功能如何帮助您的 App 提高工作效率。
资源
- Adding hardware keyboard support to your app
- Adding Menus and Shortcuts to the Menu Bar and User Interface
- Enhancing your iPad app with pointer interactions
- Human Interface Guidelines: Pointing devices
- UIKit
相关视频
WWDC21
WWDC20
WWDC19
-
下载
♪低音音乐播放♪ ♪ 克里斯多尼根:嗨 我是克里斯 我是UIKit的工程师 之后我的同事阿南特和穆罕默德 也会加入我们 iPad是许多人的主要设备 因为它有强大能力和功能 本视频中 你会学到一些 令人兴奋的新功能 你应该用这些新功能 让你的iPad应用程序更上一层楼 首先 我会介绍多任务和场景的 最新改进 然后阿南特会逐步讲解 键盘快捷键的强大改变 最后 穆罕默德会介绍系统指针的 最新改进 我们开始介绍多任务吧 iPadOS 13引进了能够 运行多个应用程序UI实例的功能 iPadOS 15中 我们进一步改善这个体验 引进了场景演示的新API 以及增强了状态还原 这些增强是基于 既有的UI场景基础结构 如果你还没采用UI场景 请看《在iPad上引入多个窗口》 我们先简短介绍关键概念 一个场景代表应用程序UI的单一实例 一个场景的组件结构 由场景配置定义 它至少会定义场景的角色和委托类 你也可以提供一个名称 情节提要 和场景子类 场景配置可在Info.plist声明 或是在运行时 用UISceneConfiguration对象创建 一个场景的内容 由一个NSUserActivity表示 这些活动用于请求场景 还有用于状态还原 一个场景由一个场景委托管理 场景委托负责设置UI 响应生命周期事件 以及存储和还原状态 最后 一个场景由一个场景会话跟踪 场景本身在背景时 可以被系统断开连接或重新连接 场景会话会跟踪场景 无论它的连接状态如何 并在不同次启动之间持续 会话可以视为 系统应用程序切换器中的表示 切换器中的每个项目 都和一个场景会话相应 向系统请求一个场景时 你可以提供一个选项对象 以自定义请求 iPadOS 15有一个新的选项子类 是特别为窗口场景推出的 使用这个子类让你能指定 一种演示样式 窗口场景演示样式 会影响相较于工作区中的其他场景 场景演示的方式 有三个可能值 显著 标准 和自动 这是使用显著演示样式的场景 它在目前的工作区以模式窗口演示 背后的其他场景变暗 因为它是模式窗口 它应该提供“取消”、“关闭” 或“完成”按钮 这个新样式可以想成 新场景的暂存区 它跟其他场景一样可以用 新的多任务控制改变位置 也可以移到应用程序架上 晚点再用 考虑这个样式是否适合某个场景时 有一些要记得的准则 首先 显著场景应该本身就有用途 它不应该被用来 为别的场景提供选项或工具 它应该提供“完成”或“关闭”按钮 而且应该专门用于 应用程序中的特定内容 像是一个文档或文件 这个专用内容范围必须在 场景的激活条件中定义 要了解更多关于激活条件 请看《使用多个窗口定位内容》 这个例子显示Safari如何开启 一个使用标准样式的新场景 它们并排演示 让人跟两者都能交互 而且两个都提供主场景的完整功能 你也可以指定样式为自动 这个值告诉系统 它应该基于它被请求的方式 选择最佳样式 iPadOS 15不仅提供 用样式自定义演示的方式 它也让人能更轻易在新场景中 打开内容 在Mac上 很常会在上下文菜单中 看到“以新窗口开启”项目 你应该将这个大家熟悉的体验 引进你的iPad应用程序 方式是用 UIWindowScene.ActivationAction 这是新的UIAction子类 用来请求 可以用在菜单 按钮和栏按钮项目的场景 要将这个功能添加到你的应用程序 先初始化一个 UIWindowScene.ActivationAction 它会被当项目被选择时 执行的闭包初始化 闭包应该返回一个激活配置 里面有新场景内容的用户活动 最后 将操作放进菜单就完成了 在iPad和Mac Catalyst上 菜单会显示“以新窗口开启”项目 被选定后会演示一个新场景 在iPhone上 这个项目会自动隐藏 因为iPhone不支持多场景 如果你宁愿在它的位置 显示另一个项目 你可以提供备用操作 我们更新先前的例子来提供备用操作 要做到这件事 先创建备用操作 当多窗口不可用 它会显示新操作的标题和图像 然后更新场景激活操作的 初始值设定项 方式是将新操作输入为备用 提供一个备用操作后 iPad和Mac Catalyst上的菜单 还是会显示“以新窗口开启”项目 但在iPhone上 则看到“显示细节”项目 这一切不需要在你的代码中 放入任何条件就能做到 添加这些菜单项目 能让人用明确又熟悉的方式 在新场景中打开内容 但这不是唯一方式 iPad是触控优先的设备 能轻易用一个手势打开场景 在笔记应用程序中 双指放在单元格上分开双指 能在新场景中打开笔记 场景会交互式动画 从单元格到最终位置 在应用程序中 提供这个功能的方式有两种 如果你使用集合视图 有个新的委托方法 至于其他视图 请用UIWindowScene ActivationInteraction 这两个方式都只是为了 演示显著样式的场景 要在你的集合视图支持这个手势 执行新委托方法 名称为 sceneActivationConfiguration ForItemAt indexPath 这很类似稍早提过的上下文菜单例子 创建新场景内容的用户活动 然后用那个活动 返回激活配置 你可能不想让每个单元格 都支持开启新场景 为了从一开始就阻止手势 返回nil就好 要在其他视图上支持手势 创建一个UIWindowScene .ActivationInteraction 创建它的闭包接受两个参数 交互本身 还有交互在视图的坐标空间中的点 这些可以用来为视图的不同区域 创建特定用户活动 闭包应该返回一个激活配置 和用户活动 交互也采取一个错误处理程序 虽然在不支持多场景的平台上 交互会被禁用 但可能还是会因为配置问题产生错误 或是因缺少系统资源产生错误 你应该已经注意到所有这些 演示窗口场景的方式 使用同样的激活配置对象 它的唯一需求是一个用户活动 但它也包含场景请求选项 和一个目标预览 如果没有提供这些 系统会尽量为你创建 不过 你可以明确提供它们 以让体验更细致 作为例子 这些集合视图单元格 有缩略图 标题和描述 当用户在单元格上分开双指 会出现一个场景 它主要显示 缩略图内容的较大版本 注意这个新场景 是整体从单元格过渡而来 如果它只从缩略图过渡 看起来会比较好 要达到这个效果 跟之前一样创建激活配置 然后检查单元格是否为缩略图单元格 如果是 用单元格的缩略图视图 创建一个目标预览并在配置上设置 通过在激活配置上提供自定义预览 过渡会细致很多 我们来看细节 现在 与其从单元格本身过渡 它会从缩略图过渡 让单元格其余部分留在原处 激活配置让你能请求 你的应用程序的任何一个场景 不过 你可能单纯只想显示一个文件 不想自己构建一个场景 iPadOS 15让你能轻松做到这点 UIWindowScene .ActivationConfiguration 有个特别子类 叫做QLPreviewScene ActivationConfiguration 返回一个预览场景配置 会请求一个系统管理的预览场景 不需要担心场景委托或回调 但你的应用程序必须在Info.plist中 声明支持多窗口 提供优美并方便的方式 让人能在新场景中打开内容很重要 但同等重要的是 存储和还原场景的状态 让人之后能够流畅返回场景 当一个场景移动到背景 系统会向场景的委托 要求一个NSUserActivity 以表示它的状态 这个活动可以是根视图控制器的活动 如果它支持“连续互通” 或是当下创建一个 这里 一个用户活动被创建 将文本字段的内容 存储在活动的用户信息字典 要提供最佳体验 场景状态应该不仅是内容而已 你也应该存储视觉和交互状态 像是滚动位置 光标位置 和第一响应者状态 与其将每个都独立存储 UITextField和UITextView现在有 一个interactionState属性 这个属性提供包含所有交互状态的 单一对象 对象中并没有内容本身 它是让你存储用户活动的 额外信息 我已通过存储文本字段的交互状态 更新先前的例子 现在 通过存储内容以及交互状态 用户活动包含足够信息 能够准确地还原 在iPadOS 14上 要还原状态可能有点困难 如果你试图在场景连接时还原状态 你会发现情节提要和视图 没有完整加载 如果你晚点再还原 在场景过渡到前景时再还原 你必须跟踪那是不是第一次 iPadOS 15解决了这个问题 它推出了新的委托方法 专门用于还原状态 它在场景连接 并且情节提要已经加载后调用 不过是在第一次过渡到前景之前调用 无论你的应用程序是否使用情节提要 你应该用这个新回调来还原状态 从存储在先前例子中的活动 还原状态 要从系统调用场景还原交互状态开始 接着 还原文本字段的内容 最后 还原它的交互状态 关键是让内容 在交互状态之前还原 最后 同步还原状态可能很复杂 你可能需要访问数据库或加载文件 而不想要在那个期间出现空的UI 考虑到这点 iPadOS 15让你的应用程序能请求 一个短期扩展 在这个扩展期间 启动图像会保持可见 同时让主要RunLoop还是能执行 一旦你的内容加载完毕 应用程序应该发出信号 完成还原 这个扩展很短 不应用于 可能长时间运行的任务 例如网络访问 如果你的应用程序未能发出完成信号 或是花太多时间 它会被系统关闭 要使用扩展状态还原 先向场景请求一个扩展 然后开始你的异步工作 一旦内容加载完毕 将它还原并叫场景完成还原 场景接着会显示刚还原的UI 在应用程序中支持多任务 能让它脱颖而出 但是要成为真正更上一层楼的 iPad应用程序 还有更多要做的 现在交给阿南特 阿南特贾恩:谢谢克里斯 我是阿南特 我是UIKit工程师 大家很喜欢iPad够轻 能够拿在手上 但同时还是能够 随时连接实体键盘 大家预期你的应用程序 能提供极佳的键盘支持 跟上他们的步调 iPadOS 15推出了几个新功能 让你的应用程序的键盘快捷键 更上一层楼 iPadOS 15拥有用来发现键盘快捷键的 全新界面 它将每个命令组织成熟悉的类别 让你的应用程序的 iPad和Mac Catalyst版本 越来越一致 菜单提供方便的搜索功能 可以在系统的任何地方叫出 你甚至可以点击快捷键来触发它 如果你需要复习如何用UIKeyCommand 支持键盘快捷键 包含命令如何 调度到响应链 请看《在你的应用程序中 支持硬件键盘》 在Mac Catalyst上 每个应用程序都有一个全局菜单 叫做主菜单 显示于 屏幕顶端的菜单栏 主菜单由几个类别子菜单组成 像是“文件”和“编辑” 这些类别菜单 则包含更多子菜单 里面含有应用程序的所有命令 iPadOS 15上 我们将 主菜单系统带到iPad应用程序 新快捷键界面 会在按住Command键不放时出现 它会显示这个菜单 和Mac上相比 主菜单系统在iPad上 表示的形式不太一样 Mac显示每个类别里面的 完整子菜单层次结构 而iPad则会将那些层次结构扁平化 禁用和无法执行的命令 在Mac上以显示为灰色表示 而在iPad上则会被隐藏 此外 iPad快捷键菜单 设计来协助用户发现 应用程序中的键盘快捷键 它不会显示无键命令 但是Mac菜单栏会 默认情况下 主菜单包含 所有系统类别菜单 像是“文件”和“编辑” 这包含所有系统命令 像是“撤销”和“重做” “粘贴”和“匹配样式”等 iPadOS 15添加“打印”命令 到系统命令 应用程序可以取得打印命令 方式是添加UIApplication SupportsPrintCommand键 到Info.plist 因为现在iPadOS 15上的 iPad应用程序支持主菜单系统 他们可以用UIMenuBuilder API 进行自定义 就和Mac Catalyst应用程序一样 事实上 如果你的应用程序已经有 Mac Catalyst版本 你已经完成了大部分的工作 应用程序应该用生成器 将所有支持的键命令添加到主菜单 这和之前应用程序 声明键盘快捷键的方式不同 通过替代UIResponder上的 keyCommands属性 或通过在视图控制器上调用 addKeyCommand(_:) 用这个方式定义的命令还是可以用 但它们会出现在新界面中另外的 未分类部分 应用程序应该移除这种键命令声明 改而将它们添加到主菜单 要自定义主菜单 在你的AppDelegate中 替代buildMenu(withbuilder:) UIKit会在应用程序启动时 调用这个方法 并传递一个UIMenuBuilder对象 应用程序应该检查生成器是否 正在修改主菜单系统 如果是 它可以用生成器自定义 假设一个应用程序想要在文件菜单中 放入一些用于选项卡的键命令 应用程序只需用UIMenu API 创建子菜单 并添加想要的键命令 作为那个菜单的子项 然后应用程序对生成器对象 调用insertChild方法 将子菜单插入文件菜单 要引用主菜单系统中的既有元素 像是此例中的文件菜单 应用程序应该指定元素的标识符 内置的系统菜单标识符 在UIMenu.Identifier定义为常量 应用程序也可以轻松创建 自己的菜单类别 这里 应用程序 创建一个“书签”菜单 跟之前一样是用UIMenu API做到 然后应用程序用生成器 将那个菜单插入根菜单 在这个例子中 是在系统视图菜单之后 就这样! 生成器可以用来 插入更多东西到书签菜单中 就像文件菜单一样 只要输入新菜单的标识符 那会由UIKit自动生成 我一直提到标识符 UIMenuBuilder会强制每个 主菜单系统中的元素都有唯一标识符 包含个别命令 假设一个应用程序插入键命令 以显示内容 可能是清单或是网格形式 两个命令是用同样的操作 changeViewMode(_:) 主菜单系统中 命令通过它们的操作隐式识别 所以这两个命令共用相同的标识符 UIMenuBuilder不会让 这两个命令都插入 除非它们有不同的标识符 区分这些命令的方式之一 是给它们不同的propertyList值 但更好的方式是给每个命令 一个唯一操作 形容它的具体用途 生成器也强制主菜单中的 键盘快捷键组合 必须是唯一 假设应用程序插入 “获取信息”键命令 按键是Command-I 因为文本样式菜单中的系统斜体快捷键 拥有同样的快捷键 这次插入也会失败 跟之前一样 有两个解决方案 应用程序可以将获取信息快捷键改成 不要和既有快捷键抵触 例如Control-Command-I 或者 若不需要文本样式命令 应用程序可以叫生成器移除它 如果插入包含一个复制 那UIMenuBuilder会使插入失败 并在控制台记录错误 显示复制的键命令或是共享的标识符 如果你发现你的插入没有出现 可能是在某处有复制 所以请在控制台找像这样的纪录 在buildMenu(with builder:)调用 结束后 应用程序的主菜单出现在 Mac菜单栏和iPad快捷键覆盖中 不过有一个问题 应用程序添加了一个有命令的子菜单 以用名称或日期分类书签 但因为iPad快捷键覆盖 不会显示子菜单层次结构 不确定这些快捷键在iPadOS上会是怎样 对于这种情况 对键命令设置一个 更具描述性的 更容易发现的标题 若两者都提供 iPadOS首选可发现性标题 而非常规标题 我之前提过单个响应者 应该避免声明键盘快捷键 而该在主菜单系统中声明 不过 响应者还是应该实现 主菜单命令的操作方法 当键命令被触发 UIKit自动将操作调度给响应者 UIKit通过遍历应用程序的响应链 做到这点 一旦它找到可以执行操作的响应者 它会调用该响应者的操作方法 如果响应链中没有东西可以执行操作 那键命令就不可执行 如果你不熟悉UIResponder概念 《在你的应用程序中支持硬件键盘》 很好地介绍了它的运作机制 《出色的Mac Catalyst 应用程序的特性》 也介绍了更多细节 当UIKit在响应链中执行响应者搜索 它调用两种好用的UIResponder方法 应用程序可以在响应者中 替代这些方法 以改善它们的键命令 第一个方法是canPerformAction (_:withSender:) UIKit用它来检查 响应者是否可以执行某个操作 默认情况下 如果响应者实现那个操作方法 这会返回true 否则它会返回false 响应者可以替代它以添加自定义逻辑 例如 网页浏览器可以告诉UIKit 若没有开着的选项卡 则closeTab命令不可执行 既然UIKit无法为那个操作 找到目标响应者 该命令则不可执行 不会在快捷键界面中显示 注意这个方法的替代 必须为未处理的案例调用super 另一个好用的方法是 validate(_ command:) 当UIKit找到键命令的目标响应者 它会在响应者上调用这个方法 并传递命令的副本 响应者可以替代 validate(_ command:) 为应用程序的当前状态 更新命令的外观 这里 应用程序更新 toggleBookmark(_:)命令的标题 基于当前页面是否加入书签 这个方法中设置的标题 会在快捷键界面出现时反映 iPadOS 15中 UIKit推出了 响应链的重大变更 当应用程序用聚焦系统 采用键盘导航 那响应者遍历会从聚焦项目开始 而不是从第一响应者开始 这个改变和键命令十分搭配 例如Photos让用户 能只用键盘使用照片库 当用户聚焦在在格子中的某个单元格 他们可以点击空格键查看那张照片 他们甚至可以按Command-C复制照片 贴到另一个应用程序中 每个单元格实现不同的键命令操作 而因为响应者遍历始于聚焦项目 键命令会锁定单元格 简单来说 聚焦系统让键命令 和响应链更上一层楼 善加利用它 以在你的应用程序中 支持强大的上下文快捷键 要了解更多 请看 《聚焦iPad键盘导航》 最后 iPadOS 15和macOS 12 推出了键盘快捷键本地化 当你用这些SDK建构你的应用程序 系统会自动本地化快捷键修改器 和每个键盘布局的输入 例如Command-反斜杠快捷键 虽然这个快捷键在美国键盘上可用 它不可能在日本键盘上执行 因为日式键盘的布局没有反斜杠键 所以系统会为日式键盘重新映射快捷键 这代表应用程序不应该 本地化快捷键修改器或输入 而是应该让系统处理 应用程序也可以选择禁用自动本地化 可以是在应用程序范围 或是在个别快捷键中禁用 当系统将快捷键本地化 它也会为右到左布局进行镜像处理 例如 一个快捷键若用Command-左括号 往回导航 它会被翻转成Command-右括号 如果快捷键不应该被镜像处理 将命令的 allowsAutomaticMirroring属性 设置为false 以禁用镜像 但不完全禁用自动本地化 iPadOS 15对于键盘快捷键来说 是重大发表 除了我们今天谈到的一切外 还有一大堆其他增强 现在你采用了所有新的多任务功能 也打造了很好的键盘支持 现在穆罕默德会给你一些建议 让你的iPad应用程序能登峰造极 穆罕默德吉斯阿威:谢谢阿南特 嗨 我是穆罕默德 我们来聊聊 iPadOS 15为系统指针带来的改进 iPadOS 13.4引进了自适应系统指针 连接起iPad基于触控的UI 和精准的鼠标或触控板 如果你不熟悉指针交互 花点时间熟悉它 观看这些之前的视频 《为iPadOS指针构建》 讲解指针交互API 《iPadOS指针的设计》 深入介绍背后的设计哲学 并讨论在应用程序采用它的最佳实践 iPadOS 15带来一些 熟悉的Mac用户交互 并且是以符合iPad的 设计语言的方式引进 它也引进一些新概念 能增强可用性和清晰度 第一个添加的是波段选择 新的指针特定多选体验 用过Mac的人应该都很熟悉 iPadOS 15中 当你在集合视图中点击并拖动 指针会伸展成一个长方形 集合视图会选择 长方形包含的项目 当然这能自然地移植到 Mac Catalyst应用程序上 类似的Mac UI 这个交互内置于 非列表UICollectionView 若UICollectionView 通过shouldBeginMultiple SelectionInteraction API 支持一指和两指多选手势 则它会自动在iPadOS 15中 获得这个行为 对于UICollectionView以外的东西 新的 UIBandSelectionInteraction API 让你能轻松在应用程序采用这个体验 既然选择逻辑完全由你决定 你能够支持自定义选择行为 并让你的UI针对改变的选择 做出任何反应 首先 用一个选择处理程序 将交互实例化 选择处理程序在指针移动 交互状态改变时调用 一旦创建后 将交互添加到你的视图 就像你会为任何其他UI交互做的一样 在处理程序中 你可以实现 你的自定义选择逻辑 方式是响应 交互状态和selectionRect中的变化 这里有个简单的实现 将选择设置为 指针移动时 交互的selectionRect里面的项目 然后它会在主要鼠标按钮放开 交互结束时 完成选择会话 除了基本选择 UICollectionView的内置波段选择 支持一些常见键盘快捷键 且为开箱即用 例如开始拖动时按住Shift不放 会导致项目被添加到现有的选择中 而不是取代当前选定的项目 按住Command不放会切换 选择长方形中的 项目的选择状态 这个可以在自定义UI中 使用交互的 initialModifierFlags属性实现 它提供开始拖动时按住不放的键 既然它是所有按下修改器的集合 你可以响应任意的键组合 以支持专属你的应用程序的 自定义或更高阶的行为 系统指针第二个添加的 是附加附件的能力 附件能沟通额外信息 并提供上下文线索 方式是将辅助形状 和主要指针结合 举例来说 在左边的例子里 两个箭头表示这个视图 可以横向拖动 在右边的例子里 加号提供一些额外上下文 给这个“加入购物车”按钮 在附件和使用自定义指针形状之间 有几个关键差异 附件在视觉上是分开的 是辅助主要指针用 这个特性在下列这点很明显 它们可以用不同的外观呈现 并和主指针分开设置动画 它们是独立的单位 可以被组合起来 并放置在指针周围 以传达不同的概念 既然它们是独立的 它们可以和任何指针样式结合 这里有个演示 示范同一组附件 也就是两个表示可拖动的箭头 可以和不同的指针样式组合 在左边 它们用提升效果组合 指针会和视图合并并将它提升 在中间 它们用突出显示效果 指针会变成圆角的长方形 并跑到视图下面 在右边 它们用新的 UIPointerStyle.system() API 和默认系统指针一起出现 所以我们能够提供额外上下文 同时保持使用最适合情况的效果 不用牺牲指针的活泼 同时还能维持它 和应用程序UI的深厚关系 就跟它设置指针样式之间的动画一样 系统会自动设置附件 出现和消失的动画 它也会流畅地设置 不同附件形状和位置之间的动画 在某个效果动作时 过渡附件可能很有意义 这种过渡可以用来沟通 底下UI状态或行为的改变 在左边的例子 从加号变成禁止符号 可能表示先前可以进行的操作 现在不再允许 指针附件由一个UIPointerShape 和一个 UIPointerAccessory.Position构成 后者将想要的位置 描述为指针中点的偏移 和距离顶端的角度 为了便利 UIKit提供一些预定义值 给指针周围的位置 如果预定义位置不太符合你的需求 你可以将它们当作起点 并自定义单个属性 这个例子一开始用topRight位置 并自定义偏移 你也可以用完全自定义的位置 像这个例子 它用自定义偏移和角度创建一个位置 要设置我们在这一部分 一开始看到的例子 我们用视图 创建一个UITargetedPreview 并用它创建一个有提升效果的 UIPointerStyle 然后我们将样式的新附件属性 设置成包含两个箭头附件的数组 UIKit提供预先准备好的箭头附件 所以我们可以用那个创建两个就好 位置在效果的左边和右边 所以现在当指针悬停在这个视图上 两个箭头会随着视图提升时往外移动 暗示它可以拖动 如果你尝试过实现 这种交互 有指针效果的视图可拖动的这种 你应该会注意到这种事 当指针到达指针区域的边缘 它会从提升视图脱离 效果会结束 通常我们想要这样 因为它能避免指针 在到处移动时黏在视图上 不过 在这种情境中 理想体验 是让指针效果维持稳定 锁定视图 在它被拖动时跟着它走 为了让这种交互更好地呈现 iPadOS 15在UIPointerRegion上 引进了latchingAxes的概念 当一个区域锁定一个特定轴 按下主要鼠标按钮时 它的指针效果会随着轴跟踪指针 横向锁定区域让你能在 X轴上随意拖动 同时在Y轴上橡皮筋式滚屏回弹 纵向的锁定区域 让你能在Y轴上自由拖动 而锁定两轴的区域 让你能在两轴都自由拖动 这些新工具可以用来构建 非常强大的新体验 这里它们被结合在 文稿编辑应用程序中 像是Pages或Keynote 这个图像可以用波段选择选定 选择这个图像让拖动指标出现 当指针悬停在指标上方 附件会出现 暗示如果被拖动的话 图像会如何调整大小 最后 锁定让指针效果和附件能够 跟踪锁定轴的调整大小手势 这些只是一些 iPadOS 15为iPad引进的增强 在适用的地方善加利用它们 让你的应用程序能发挥最大效用 采用显著场景让人能获得 你的应用程序内容的 聚焦、不间断的视图 用新的键盘快捷键菜单 让他们能快速完成复杂任务 并用新指针功能提升他们的生产力 谢谢收看 ♪
-
-
4:56 - Build an "Open in New Window" action
let <#newSceneAction#> = UIWindowScene.ActivationAction({ _ in // Create the user activity that represents the new scene content. let userActivity = NSUserActivity(activityType: <#User Activity Type#>) // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) })
-
5:43 - Use an alternate action with UIWindowScene.ActivationAction
// Create an action to use when multiple scenes are not available. let alternateAction = UIAction(title: <#Alternate Action Title#>, image: <#Alternate Action Image#>, handler: { _ in <#Perform Alternate Action#> }) // Create the scene activation action with the alternate. let newSceneAction = UIWindowScene.ActivationAction(alternate: alternateAction) { _ in // Create the user activity that represents the new scene content. let userActivity = NSUserActivity(activityType: <#Scene Activity Type#>) // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) }
-
6:58 - Present a scene from a collection view with a gesture
func collectionView(_ collectionView: UICollectionView, sceneActivationConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIWindowScene.ActivationConfiguration? { // Get the item's user activity. guard let itemActivity = <#User Activity#> else { // Return nil if item can’t be opened in a dedicated scene. return nil } // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: itemActivity) }
-
7:28 - Present a scene from a custom view with a gesture
// Create an activation interaction. let newSceneInteraction = UIWindowScene.ActivationInteraction { interaction, point in // Get the activity for specific point in view. guard let userActivity = <#User Activity#> else { return nil } // Return an activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) } errorHandler: { error in // Present the content in another manner. <#Present Content#> } // Add interaction to the view. <#View#>.addInteraction(newSceneInteraction)
-
8:53 - Customize scene transition preview
// Create the activation configuration. let itemActivity = NSUserActivity(activityType: <#User Activity Type#>) let configuration = UIWindowScene.ActivationConfiguration(userActivity: itemActivity) // If the cell has a subview to use as the preview, create the custom preview. if let cell = collectionView.cellForItem(at: indexPath) as? <#Expected Cell Class#> { configuration.preview = UITargetedPreview(view: cell.<#Subview For Preview#>) } // Return the activation configuration. return configuration
-
10:18 - Save scene state
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { guard let viewController = self.window?.rootViewController as? <#Expected View Controller Class#> else { return nil } let stateActivity = NSUserActivity(activityType: <#State Restoration Activity Type#>) stateActivity.addUserInfoEntries(from: [ // Save content of a text field. <#Content Key#>: viewController.<#Text Field#>.text ]) return stateActivity }
-
11:16 - Save scene state with interaction state
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { guard let viewController = self.window?.rootViewController as? <#Expected View Controller Class#> else { return nil } let stateActivity = NSUserActivity(activityType: <#State Restoration Activity Type#>) stateActivity.addUserInfoEntries(from: [ // Save content of a text field. <#Content Key#>: viewController.<#Text Field#>.text, // Save interaction state of a text field. <#Interaction State Key#>: viewController.<#Text Field#>.interactionState ]) return stateActivity }
-
12:13 - Restore scene state
func scene(_ scene: UIScene, restoreInteractionState stateRestorationActivity: NSUserActivity) { guard let viewController = window?.rootViewController as? <#Expected View Controller Class#>, let userInfo = stateRestorationActivity.userInfo else { return } if let content = userInfo[<#Content Key#>] as? String { // Restore the content first. viewController.<#Text Field#>.text = content // Then, restore the text field’s interaction state. if let interactionState = userInfo[<#Interaction State Key#>] { viewController.<#Text Field#>.interactionState = interactionState } } }
-
13:15 - Restore scene state asynchronously
func scene(_ scene: UIScene, restoreInteractionState stateRestorationActivity: NSUserActivity) { guard let viewController = window?.rootViewController as? <#Expected View Controller Class#> else { return } // Request an extension. scene.extendStateRestoration() // Fetch content asynchronously. <#self.someAsyncFunction#> { result in <#Restore Content#> // Signal that state has been restored. scene.completeStateRestoration() } }
-
17:15 - Modify the main menu
override func buildMenu(with builder: UIMenuBuilder) { super.buildMenu(with: builder) // Ensure the builder is modifying the main menu. guard builder.system == .main else { return } // Use the builder to modify the main menu... }
-
17:37 - Add key commands to the main menu
// Create a menu with key commands. let tabMenu = UIMenu(options: .displayInline, children: [ UIKeyCommand(title: NSLocalizedString("New Tab", ...), action: #selector(BrowserViewController.newTab(_:)), input: "t", modifierFlags: .command), UIKeyCommand(...) ]) // Insert tabMenu into the File menu. builder.insertChild(tabMenu, atStartOfMenu: .file)
-
18:19 - Add a custom menu category
// Create a "Bookmarks" menu. let bookmarksMenu = UIMenu(title: NSLocalizedString("Bookmarks", ...), children: [...]) // Insert the Bookmarks menu into the root menu, after View. builder.insertSibling(bookmarksMenu, afterMenu: .view) // Insert another menu into the Bookmarks menu. let sortBookmarksMenu = UIMenu(...) builder.insertChild(sortBookmarksMenu, atEndOfMenu: bookmarksMenu.identifier)
-
22:38 - Customizing key command performability
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if action == #selector(closeTab(_:)) { return !openTabs.isEmpty } else { return super.canPerformAction(action, withSender: sender) } }
-
23:26 - Customizing key command appearance
override func validate(_ command: UICommand) { if command.action == #selector(toggleBookmark(_:)) { if currentTab.isInBookmarks { command.title = NSLocalizedString("Add to Bookmarks", ...) } else { command.title = NSLocalizedString("Remove from Bookmarks", ...) } } else { return super.validate(command) } }
-
28:47 - Supporting multi-selection using UIBandSelectionInteraction
// Support multi-selection using UIBandSelectionInteraction. let selectionInteraction = UIBandSelectionInteraction { [weak self] interaction in guard let strongSelf = self else { return } // Handle selection by responding to interaction state. if interaction.state == .selecting { strongSelf.selectItemsInRect(interaction.selectionRect) } else if interaction.state == .ended { strongSelf.finalizeSelection() } } view.addInteraction(selectionInteraction)
-
33:01 - Customizing a predefined pointer accessory position
var position = UIPointerAccessory.Position.topRight position.offset = 40.0
-
33:14 - Creating a custom pointer accessory position
let position = UIPointerAccessory.Position(offset: 23.0, angle: .pi * 1.25)
-
33:27 - Pointer Accessories
// Attach two arrow accessories to a lift pointer effect. func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { let preview = UITargetedPreview(view: self) let style = UIPointerStyle(effect: .lift(preview)) if #available(iOS 15.0, *) { style.accessories = [ .arrow(.left), .arrow(.right) ] } return style }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。