大多数浏览器和
Developer App 均支持流媒体播放。
-
为你介绍 WidgetKit
Meet WidgetKit: 将 app 最有用的信息直接显示在主屏幕的最佳方法。我们将向你展示一个优秀小组件的必备要素,并介绍 WidgetKit 的特点和功能。了解如何创建窗口小组件,并了解 WidgetKit 如何利用 SwiftUI 的功能提供无状态体验。 了解如何利用现有的先进技术确保小组件显示相关内容。 并创建一个时间线,从而确保你能提供新鲜的内容。 有关创建窗口小组件的更多信息,请查看“为窗口小组件构建 SwiftUI 视图”和“小组件编程临摹课程”。
资源
- Building Widgets Using WidgetKit and SwiftUI
- Creating a widget extension
- Human Interface Guidelines: Widgets
- Keeping a widget up to date
- Learn more about creating widgets
- WidgetKit
相关视频
WWDC22
WWDC21
WWDC20
-
下载
(你好 WWDC 2020)
大家好 欢迎来到 WWDC (为您介绍 WIDGETKIT) 大家好 我叫 Nahir 是 iOS 系统体验团队的经理 稍后我的同事 Neil 也会加入我的行列 很高兴 欢迎各位观看 “为您介绍 WidgetKit” 在 iOS 14 中 我们有了一个引人注目的 全新主屏幕体验 即更加动态和个性化的窗口小组件 我们的新小组件设计新颖、便于快速浏览 而且非常高效 不仅能在 iPhone 的主屏幕上使用
也适合用于焕然一新的“今天”视图
能固定在 iPad 主屏幕上 同时也能用于 MacOS Big Sur 的 新颖精美的“通知中心”
不过 在我深入研究 API 之前 先让我们来讨论一下是什么造就了 一个优质的小组件体验 当团队开始考虑这个问题时 我们很快就达成了三个主要目标 一个优秀的小组件要做到 可速览、有关联性和个性化
那我们说一个优秀的小组件是可速览的 又是什么意思呢? 稍后我们就会讲到 其实新小组件是可以有多种尺寸的 特别是在小组件尺寸最小的情况下 只有大约四个主屏幕图标的空间 所以一定要充分利用这些空间 人们在主屏幕上通常只花上几分钟 就会切换到别的地方了 他们应该不需要与你的小组件进行交互 最多只要快速看一眼 就可以从小组件中获得最大的价值
请看一些小尺寸的小组件示例 你不需要轻点任何按钮 或者甚至花时间 去弄清楚一个复杂的用户界面 内容才是重点
这一点很重要 小组件不是迷你 app 你应把它看作是把你的 app 内容 投射到主屏幕上 而不是充满小按钮的完整迷你 app 有时很难弄清楚 幸运的是 我们的设计团队 有一个很棒的讲座 (设计优秀的小组件) 可以帮助你思考在 app 中的哪些部分 会带来极具吸引力的小组件体验
拥有一个可速览的小组件 只是目标的一部分 让这些小组件具有相关性也同样重要 例如 在早上准备出门时候 我最关心的是天气 但接下来 在一整天之中 也许就变成了提醒事项 让我不要忘记要做的事情 然后到了晚上就变成了音乐 这样我就可以用一些喧哗的派对音乐 来骚扰我的邻居 开玩笑的呢 而在我们的移动平台上 空间就显得尤为重要 我们想方设法地去确保正确的小组件 可以在需要的时候被派上用场
这就是智能叠放的用武之地 智能叠放是小组件的集合 它将自动旋转 以在顶部显示正确的小组件 但你也可以轻扫过去 有趣得很呢 我们使用设备端智能技术来帮助显示 叠放顶部的正确内容 但是作为开发者 你可以通过 捐赠 Siri 快捷指令来实现这一目标 这正是我们在过去几年中 一直在构建的系统 还有一个特定 WidgetKit API 以帮助系统确定 你的小组件何时更加相关 或者其它可能更相关的事物 这是一个非常深奥的主题 涉及的范围不仅包括小组件 我们有一整个讲座专门讨论这个主题 我强烈建议你去看看 一个好的小组件 应该可以让你对其进行个性化设置 让我们来看看天气小组件 没错 单位是摄氏 我是加拿大人 我们用的是公制 从一开始 就有三种不同的尺寸供你选择 小、中、大 你不需要支持所有尺寸 因为有些体验并不适用所有尺寸 但我建议支持尽可能多的尺寸 让我们进一步定制天气小组件 在编辑模式下只需点击小组件 翻转以快速配置 所有配置选项都是使用意图构建的 你可以从快捷指令中熟悉意图 选择城市是一个相对简单的例子 但这是一个充满可能性的强大系统 而 WidgetKit 的最棒之处在于 我们可以完全 自动地从你的意图 生成整个配置用户界面 无需额外工作 真的很酷 来回顾一下 一个优秀的小组件体验的目标 是可速览、相关、个性化的
按照这些目标 我们设计出 WidgetKit 让我们来一起看一下 首先 小组件从一开始 就是一个多平台的目标 让开发者尽可能容易地 在 iOS、iPadOS 和 macOS 上 应用他们的知识 小组件的用户界面和 WidgetKit 完全是用 SwiftUI 构建的 SwiftUI 还使它能够轻松地 将近自动支持 动态类型和深色模式等功能
其次 将小组件放在主屏幕上 可产生强烈影响 一般人一天要去主屏幕90多次 而且只在那里短暂停留
你最不想看到的就是 主屏幕上满是加载中的标识符号 当在 watchOS 上设计复杂功能时 我们有非常相似的目标 即让东西准备就绪 且能够立即可速览 所以我们从它们的构建过程中获得了灵感
这意味着 WidgetKit 扩展是在时间线中 返回一系列视图层次结构的后台扩展 利用 SwiftUI 的声明性 我们可以在此时间线中打包这些视图 然后将它们发送到主屏幕上 主屏幕会根据时间线 在正确的时间呈现它们 这就避免了在整个启动过程中 进行加载 然后呈现到视图 它们都准备好了 立即可速览
我们预先准备好了视图这一点 意味着我们也可以 在系统的其他区域再次使用它们 例如 要拥有这种超级有趣的体验 可从小组件图廊中添加小组件 然后使用该 app 的人 可以在主屏幕上预览小组件 你可以从主 app 中刷新这些时间线 例如 若你的 app 发生了变化 那么小组件就会进行更新
也可以直接从扩展中设置更新时间
例如 日历小组件知道我当天的所有事件 以及事件发生的时间 扩展可以使用这些信息来呈现正确视图 来显示我下一次会议时间 当我正在开会时 就会继续呈现到下一个视图 你知道吗? 我觉得 Neil 和我不该喝咖啡 而是去城里我最喜欢的牛排店庆祝一下 所以我进入日历并更新事件
然后日历将使用 API 来重新加载时间线 我们的意思是 扩展唤醒并返回一个包含所有新更新的 新的时间线 现在你已经对 WidgetKit 的工作原理 有所了解 让我把它交给 Neil 他将深入讲解如何构建一个小组件 Neil 该到你了 谢谢 Nahir 大家好 我叫 Neil Desai 是 WidgetKit 的工程经理 今天我很高兴可以讨论 如何创建一个优秀的小组件 为此 我想谈四件主要的事情 我们将讨论如何定义一个小组件 并学习如何创建一个可速览的体验 我们还将讨论小组件的引擎 我们的视图、时间线和重新加载 以及小组件的个性化和智能化方面 小组件非常简单 但功能却非常强大 我们要谈谈你可以使用的所有工具 首先 让我们学习如何定义小组件 要定义小组件 我想先讨论几个不同的概念 分别是种类、配置、 supportedFamilies 和占位符
当我们开始这个项目时 我们讨论了很多 想要启用的不同类型的小组件 我们想要一个机制 以允许单个扩展支持多种小组件
例如 单个股市扩展提供了 类似股市概述小组件的体验 这是一个很棒的小组件 它提供了一些股票的简单信息 而且 同样的扩展也为 股市细节小组件提供了动力 允许用户在主屏幕或 在 MacOS 上的 Notification Center 上 显示单个股票 正如 Nahir 所提到的 这一切 都是通过使用 SwiftUI 和一个多平台扩展完成的 WidgetKit 扩展可支持 SwiftUI、AppKit 和基于 Catalyst 的 macOS app 各种小组件还可以显示 其可支持的类型配置 一个是 StaticConfiguration 另一个是 IntentConfiguration
健身小组件对于配置来说 并没有什么意义 小组件不需要允许用户 以任何方式配置它 这个小组件实际上只能显示你今天的运动 而我刚好没有什么活动 我该多运动一下 但我离题了 总之… 这个小组件 有一个 StaticConfiguration
另一方面 提醒事项 可以针对特定列表进行个性化设置 这个小组件使用 IntentConfiguration
一种特定类型也能启动 使一个或多个 supportedFamilies 默认情况下 小组件支持所有家族类型 天气小组件支持所有家族 所以我可以享受华氏温度下的 所有家族类型 而不是像 Nahir 所喜欢的摄氏温度 这些家族在 iOS 上看起来很棒
在 maCOS 上也很棒 小组件定义的最后一个关键组件 是占位符用户界面 每种小组件都需要提供 一个占位符用户界面 占位符用户界面是小组件的默认内容 它应该是小组件类型的表示 但仅此而已 此用户界面中不应该有任何用户数据 另一个需要注意的重要事项是 此用户界面很少被检索 也不能保证何时会检索 通常 我们只会在设备环境更改时 要求一个新的占位符用户界面 例如 如果设备的动态类型设置更改
优秀的占位符用户界面显示了 你的小组件类型的表示 我鼓励你为你的小组件 考虑最好的用户界面 对了 编程 所有这四个关键组件 都归结为这个小组件定义 这里我们定义了一个示例小组件 它符合小组件协议 我们指定了一个种类 我们返回一些小组件配置中 里面有几个项 在小组件设置中 我们在占位符视图中指定一个提供者
我们稍后再谈提供者 但实际上 这就是我们 如何设置小组件的引擎的
现在知道如何定义我们的小组件了 下面我们就来说说 如何创造一种可速览的体验 天气是一个可速览小工具的好例子 Nike Run Club 的小组件和日历也一样 这三个小组件都向我显示有用的信息 并邀请我这个用户 轻点启动 app 并找到更多信息 创建可速览体验的第一个方面 就是创建 StatelessUI SwiftUI 对此是最贴切的完美选择 主屏幕和 Notification Center 上的 这些交互是不同的 这些小组件不是迷你 app 我们不支持在小组件内滚动 不支持诸如开关 和其他系统控件之类的交互元素 也不支持视频或动画图像 这些可速览用户界面允许你的用户 轻松点击小组件 和深度链接到你的 app 中 让我们以音乐为例 这是 systemSmall 中的音乐 用户可以点击最近播放的专辑 并直接深度链接到该 app 中 systemSmall 有一个单点击目标 整个小组件都是点击目标 目的是让用户直接进入 app
音乐的小组件也支持 systemMedium 有很棒的内容 给我展示了一堆不同的专辑 每个专辑都是一个单独链接 可以带你直接进入该 app
可以使用小组件 URL API 将整个小组件与 URL 链接相关联 如果要在 systemMedium 或 systemLarge 中创建子链接 那么你可以使用新的 link API 和 Swift API (点击交互)
我们已经定义了小组件 并知道如何创建一个可速览的体验 现在让我们谈谈小组件体验的真正本质 视图、时间线和重新加载 的确 有三种类型的 用户界面体验需要你去考虑 前面讨论过的占位符用户界面 然后是 snapshot 和时间线 Snapshot 是系统需要 快速显示单个条目的地方 因此希望扩展能够快速返回一个视图 越快越好 因为当你这样做的时候 你将在 iOS 华丽的小组件图廊中 看到你真正的小组件 这不是在设计时必须提供的截屏或图像 这是你在 iOS iPadOS 和 macOS 上的 真实小组件体验
在大多数情况下 时间线的第一个条目和 snapshot 可以作为相同的条目返回 所以你在小组件图廊中看到的 就是当用户 将它添加到他们的设备上时的画面 按照这个思路 如果 snapshot 只是一个条目 那么在正确的时间显示的一系列多个视图 就是时间线 时间线是返回的视图和日期的组合 它允许你指出 应该在什么时间显示特定视图
我们就是通过返回一个时间轴 来驱动小组件体验的 时间线的返回应该支持黑暗和浅色模式 我想再多介绍几句 当 WidgetKit 扩展返回条目时 我们将获取该信息 并将视图层次结构序列化到磁盘 (WidgetKit 扩展、小组件) 这意味着我们实时呈现每个条目 这使得系统能够在多个时间线上 同时运行多个小组件 我要强调 这可是个很酷的技术 时间线通常应返回数天的内容 有些时候 你的小组件 需要返回更多的更新信息 要通过我们称之为重新加载的概念 来做到这一点
重新加载时 系统将唤醒你的扩展 并为放置在设备上的每个小组件 要求一个新的时间线
重新加载有助于确保你的内容 对你的用户始终是更新的 我不知道你会怎么想 但我经常发现代码 是了解一个新主题最简单的方法 那么 让我们深入了解吧 这是 TimelineProvider 协议 我们有一个 TimelineEntry 它主要由日期 提供环境信息的环境 以及系统要求输入的环境组成
然后 在 snapshot 函数 系统要求输入一个条目 在时间线函数中 系统要求输入一系列条目
下面是如何遵守 TimelineProvider 协议的示例 我会创建一个提供者 在 snapshot 中 我将创建一个条目并返回它 在时间线中 我将返回一个条目数组 并附加一个重新加载策略 每个时间线中都包含一个重新加载策略 在这里你可以告诉系统 什么时候要求下一个时间线
你可以要求在你提供的时间线末尾 在特定日期之后重新加载 也可以明确地告诉系统 不要重新加载你的时间线 例如 可能你的小组件首先需要用户数据 然后才能返回一个令人信服的时间表 系统将考虑你的重新加载策略 并确定重新加载小组件的最佳时间 经常查看的小组件 将获得更多的重新加载 不经常查看的小组件 将会收到更少的重新加载 当设备环境发生变化时 系统还将强制重新加载 例如 如果发生了重大的时间变化
系统将确定重新加载小组件的最佳时间 但也有其他事件 这可能需要请求系统 从你的 app 中重新加载
例如 你的 app 可能会收到后台通知 或者用户可以在 app 本身进行更改 当收到后台通知时 你可以通过 WidgetCenter 使用 WidgetKit API 来重新加载时间线 以唤醒扩展 如果你的用户在你的前台 app 中 做了相关的更改 你也可以重新加载你的时间线
重新加载前台 app 时要谨慎 只有当 app 中发生相关更改时 才重新加载你的小组件 这些更改应该反映在你的小组件中 你可以在 app 进程或扩展中 使用 WidgetCenter API 来重新加载时间线 你可以选择重新加载每种时间线 或重新加载所有时间线 并且可以检索当前配置的列表 很多时候 你需要查询服务器 以获取更多信息 你可以使用后台 URLSessions 来启动任务 并且你的有效负载将通过 onBackgroundURLSessionEvents 修饰器 传递到扩展 你还应该了解你的网络使用情况 确保并将请求批处理到服务器 并且只使用你所需的网络流量
当你的 app 处理器小组件在后台运行时 重新加载由系统编预算 在小组件所需的处理和联网方面 要保持效率 小组件并不在每一次操作中 小组件并不是在主屏幕上 创建实时运行体验
有许多方法可以驱动重新加载 以帮助使小组件保持最新 考虑适合你的小组件的正确体验 并记住如何以不同的方式 有效地重新加载你的时间线 最后 让我们谈谈 如何使用户能够个性化小组件 如果这对你有帮助的话 并帮助通知叠放中的智能
个性化和智能化是由两大概念驱动的意图 用作允许用户配置小组件的机制 以及相关性 即允许开发人员 通知叠放中的智能 意图都是由意图框架驱动的 意图包含一组参数 这些参数是要向用户提出的问题
例如 天气的配置问题 是返回预报的定位
意图框架已经用于 与 Siri 和快捷指令的集成 在 iOS14 中 它现在用于驱动小组件配置
我们来看一个具体的例子 股市的单一符号小组件 询问要显示哪只股票 当用户尝试配置小组件时 意图可以通过允许股市 返回与股市 app 中显示的 相同股票列表来帮助回答这个问题 这功能真的很强大 而且有助于为你的 app 提供所需的工具 为你的用户提供一个伟大的配置体验
但有时 这可能还不够 如果有人想要显示一只在主 app 中 还不存在的股票该怎么办? 好吧 多亏了意图的强大功能 我们可以使用意图的 动态选项功能来驱动这种类型的体验 因此 用户可以在配置用户界面中 进行搜索 系统将启动股市意图扩展 然后该扩展可以以股市符号的形式 返回一系列答案 在 iOS 14 中 意图现在能支持 app 内的意图处理 你的 app 可以回答这些问题
如果你想了解更多关于 app 内 意图处理的信息 请观看“SiriKit 和快捷指令中的新功能” 那么到了现在 你还记得我们前面定义的那个小组件吗? 以下就会讲到如何改变它以支持意图 现在 我们指定 IntentConfiguration 而不是 StaticConfiguration 并指定关联的意图
按照同样的思路 这里是之前的 TimelineProvider 现在则演变成符合 IntentTimelineProvider 的提供者
它将向你传递一个意图对象 根据意图中的参数 则会生成一个特定时间线
小组件最酷的特性之一 不仅是只有一个小组件 而是在一个叠放中有多个小组件 系统可以智能地旋转最相关的小组件 你的 app 和小组件 可以帮助培养这种智能 有两种主要方法可以做到这一点 当用户在你的 app 中执行动作时 你的 app 可以捐赠快捷指令 如果你的小组件 是由相同的 INIntent 支持的 则你的小组件 在用户通常可能会执行该操作时 会在叠放中旋转 如果你想了解更多信息 我建议查看 “启用小组件个性化和智能”讲座 你的小组件扩展还能够通过 TimelineEntryRelationship API 用相关性值注释时间线条目 当时间合适 并且你觉得条目是最相关的时候 则可以返回分数和持续时间 来通知系统旋转到你的特定小组件 分数是由你提供的浮动值 相对于你曾经提供的所有条目 持续时间是特定条目相关的时间间隔
我想确定并强调一下 这个相关性值是相对于 你曾经提供过的所有条目 系统将获取你的相对值 和所有其他小组件的值 并确定要在堆栈中旋转到的正确小组件
小组件非常简单 但功能却非常强大 我们不过是简单介绍了一下 小组件并不是迷你 app 所以要为用户考虑可速览的体验 并使用时间线 重新加载的概念和智能 以在 iOS、iPadOS 和 macOS 上 创造完美体验
代表 Nahir 和我本人 我们希望各位参加 WWDC 愉快 我们迫不及待地想看你所构建的内容
-
-
11:01 - StaticConfiguration Widget definition
@main public struct SampleWidget: Widget { private let kind: String = "SampleWidget" public var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in SampleWidgetEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") } }
-
15:51 - TimelineProvider example
public struct Provider: TimelineProvider { public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date()) completion(entry) } public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let entry = SimpleEntry(date: Date()) let timeline = Timeline(entries: [entry, entry], policy: .atEnd) completion(timeline) } }
-
20:45 - IntentConfiguration Widget definition
@main public struct SampleWidget: Widget { private let kind: String = "SampleWidget" public var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self provider: Provider(), placeholder: PlaceholderView()) { entry in SampleWidgetEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") } }
-
20:54 - IntentTimelineProvider example
public struct Provider: IntentTimelineProvider { public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let entry = SimpleEntry(date: Date(), configuration: configuration) // generate a timeline based on the values of the Intent completion(timeline) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。