-
WidgetKit 基础知识
小组件可在系统各处突出显示你 App 中最重要的内容,让用户有更多机会与你的 App 交互。探索不同类型的小组件,以及令人印象深刻的小组件需要具备的特质。了解如何创建小组件、让小组件保持最新状态,并利用 App Intents 和动态样式为用户提供自定义小组件的选项。
章节
- 0:01 - Introduction
- 1:03 - Fundamentals
- 13:15 - Integrate with your app
- 17:04 - Adapt with the system
资源
相关视频
WWDC26
WWDC25
WWDC23
WWDC21
-
搜索此视频…
你好 我是Jonathan Long 系统体验团队的工程师 大家都喜欢使用小组件 你的小组件能在用户 最常访问的地方提供相关内容 小组件适用于 多个Apple平台 包括iOS iPadOS watchOS visionOS和macOS 你的小组件能扩展App 在整个系统中的覆盖范围 为用户提供更多体验 你优秀App的机会 接下来我们深入探讨小组件 了解WidgetKit的基础知识 如何构建你的第一个小组件 以及如何保持更新 今天我将介绍小组件基础知识 以及如何构建第一个小组件 WidgetKit提供的功能 助力整合App与小组件体验 以及如何确保小组件 适应系统环境的变化 例如切换到着色环境 首先是基础知识 从什么是小组件说起 让小组件令人难忘 有三个特质 小组件要一目了然 用户应能通过快速浏览 了解你的小组件 例如天气小组件会显示 恰好足够的天气预报信息 帮助我为新的一天做准备 小组件要具有相关性 小组件内容应符合 时间 个人习惯和位置预期 日历小组件显示当前时间 的下一个日程并持续更新 全天不断更新 小组件可个性化定制 可以配置为 对你而言重要的内容 例如这个照片回忆小组件 展示我在家庭露营的美好回忆 与我的女儿们在一起 借助WidgetKit和SwiftUI 你可以提供出色的 一目了然 相关且可个性化 定制的App小组件
我正在为读书俱乐部开发一款App 以鼓励自己多读书 这款App让我追踪阅读进度 以及已读完的书目 并制定计划帮我在 下次读书会前读完一本书 我还创建了一些小组件 可直接在主屏幕查看数据 第一个是阅读目标小组件 全天鼓励我坚持阅读 第二个是阅读日志小组件 帮助我追踪 当前的阅读进度 第三个小组件为我制定计划 帮助我按时读完一本书 在整个讲座中 我将介绍每个小组件的关键决策 为你创建小组件提供参考 首先介绍App如何 向系统提供小组件 无论App使用SwiftUI 还是UIKit构建 都可以拥有小组件 而小组件将以SwiftUI构建 你的App通过小组件扩展 向系统提供小组件 该扩展作为独立进程运行 与App相互分离 由于小组件扩展 作为独立进程运行 你需要使用共享容器 通过App Group将数据 从App传递给小组件扩展 例如共享数据库 或User Defaults 小组件扩展专注于小组件本身 系统和WidgetKit与扩展交互 获取小组件所需的数据 WidgetKit向扩展请求内容 这些内容称为时间线
时间线由多个时间线条目组成 WidgetKit将这些条目作为数据 提供给小组件的视图 生成的视图将被归档 系统在相应时间显示这些视图 Widget Extension将小组件 暴露给系统 并提供时间线内容 时间线是一系列条目 每个条目携带所需数据 用于在特定时间点 渲染小组件视图 接下来看看阅读目标 小组件的代码 将这些部分整合在一起
当你为小组件扩展 创建新Target时 Xcode会为你生成一个小组件 对于简单的小组件 这个实现已包含很多所需内容 从提供WidgetConfiguration 的body开始
小组件有两种 配置类型 AppIntentConfiguration 和StaticConfiguration AppIntentConfiguration适用于 可由用户配置的小组件 我的小组件会根据 当前阅读的书自动配置 所以我使用最简单的 StaticConfiguration StaticConfiguration需要 几个参数 Kind 即该类小组件 的唯一标识符 时间线Provider负责为小组件 生成时间线条目 以及一个闭包 接收时间线条目 并返回View 在闭包中 你为提供的 时间线条目返回SwiftUI视图 由于我的读书俱乐部App 使用SwiftUI构建 已有现成视图 DailyReadingGoalView 正是小组件所需的视图 为了指定背景 我使用了containerBackground修饰符 该修饰符标识了 小组件的背景视图
当用户为设备设置 彩色或透明着色效果时 系统可以将指定的 背景视图替换为 玻璃材质视图 阅读目标小组件的 视图部分已实现 接下来深入了解 时间线Provider的职责 以及时间线如何 保持小组件的相关性
时间线Provider提供条目 代表小组件的三种独立状态 Snapshot Placeholder 和Timeline
Snapshot是小组件 的真实预览 这是用户在小组件库中看到的内容 这是小组件给用户 留下深刻第一印象的机会 在用户开始使用前App没有数据 所以我展示了一本热门书籍 Atomic Habits 并附上默认消息 让用户想象小组件 最佳状态下的样子 在将其添加到主屏幕之前 Placeholder是小组件暂无内容时 系统显示的占位视图 尚没有内容可显示时 例如首次加载时间线时 由于占位视图需立即显示 获取它必须是同步操作 因此请提供无需从磁盘 或网络获取数据的占位视图
对于阅读目标小组件 我使用了SwiftUI的redacted修饰符 它会创建我的View 的简化版本
时间线条目是小组件 在特定时刻显示的内容 可以是当下 也可以是未来的任意时刻 时间线Provider会生成 一组这样的条目 系统在各自对应的时间 渲染每个条目
阅读目标小组件的时间线条目 包含小组件所需的全部信息 以渲染此视图 包括励志消息 已读页数 书名和封面名称
这个小组件的大部分数据 都保持不变 除了励志消息 该消息会在全天各个时间更新 在讲解时间线时 我只展示会随时间变化的消息 它会随时间的推移而变化 时间线对于保持小组件内容更新 至关重要 每个时间线条目提供 渲染小组件视图所需的数据 此时间线当前显示两个条目 一个在上午9时 另一个在上午11时30分
阅读目标小组件 所需的核心数据 是全天更新的励志消息 随WidgetKit推进时间线而更新
这些时间线条目被提供给小组件 以生成相关视图 在特定时间展示该条目的内容 通过新鲜内容保持小组件的相关性
在某个时刻 你的时间线 需要刷新以获取更多条目 这称为重新加载时间线 时间线通过重新加载策略 指定其重新加载行为 重新加载策略有三种选项 atEnd afterDate和never 我们来讨论每种策略 及其适用场景
atEnd策略表示 时间线应在耗尽后重新加载 当所有时间线条目 全部显示完毕时
全天将渲染新的时间线条目
当下午1时最后一个条目显示完毕后 系统会触发重新加载 向小组件扩展请求更多内容
你的小组件扩展将提供 包含更多条目的新时间线 阅读目标小组件会在不同时间 更新励志消息 全天不断更新 atEnd策略适合此场景 因为该小组件提供一系列条目 结束时间不是固定的 且需要在时间线耗尽时重新加载
afterDate策略允许你 为小组件指定具体的重新加载日期 以请求重新加载 以下是驱动阅读日程小组件 的不同时间线条目 我已隐去完整数据集 只保留随时间变化的章节信息 随时间推移而变化 阅读日程小组件提供两个时间线条目 指示所需阅读的内容 包括今天和明天需要阅读的内容 该小组件需要 在当天结束时重新加载 以重新计算后续日程 它使用afterDate重新加载策略 指定当天结束时的日期
当前时间到达该日期时 我的扩展将被重新加载 并被要求提供新的时间线
新时间线提供周二和周三的条目 包含重新计算的日程 afterDate策略非常适合 像阅读日程小组件这样的场景 当你明确知道小组件 需要重新加载的具体时间时
最后是never重新加载策略 顾名思义 小组件不会自动重新加载 当自动重新加载不适用时 可选择此策略 阅读日志小组件就是一个好例子 它只需在用户与App 或小组件交互时才刷新 当需要重新加载时 你可以通过显式调用来实现 调用WidgetCenter的重新加载API 或发送推送通知 如需深入了解重新加载策略 请观看WWDC21的 《Principles of great widgets》
以下是几个值得牢记的最佳实践 在构建和重新加载时间线时
尽可能提供多个时间线条目 确保系统始终有内容 可供你的小组件显示
WidgetKit在设计时 考虑了全天续航 因此每个小组件 都有更新预算 预算受用户查看习惯 的显著影响 并会全天持续更新 更新频率可能有所不同 系统足够智能 能够合理地为你的小组件 调整重新加载 请注意 App在前台时 频繁重新加载可能会受到限制 如果小组件数据可能已更新 在App进入后台时 最后调用一次重新加载通常是好方法 某些内容是短暂的 有明确的开始和结束日期 需要更频繁地更新 并需要提醒功能 例如体育赛事 如果符合上述情况 建议考虑构建实时活动 你可以在《Live Activities essentials》中 了解更多关于实时活动的内容 来自WWDC 26
目前我所有小组件的尺寸相同 均为system medium家族 但现在我的Widget Extension 和时间线Provider已连接好 我可以使用多种 其他小组件家族 小组件有各种 不同的形状和尺寸 建议支持尽可能多的尺寸 让使用你小组件的用户 在放置时有更多选择
System extra large portrait家族 已在visionOS 26中推出 macOS iOS和iPadOS 27的新增功能 System extra large portrait家族 现已可用 这个新家族 让使用你小组件的用户 能够更充分地展示你App的内容 读书俱乐部的阅读日程小组件 可以复用medium小组件的相同数据 我们已经实现的数据 更大的尺寸让我可以了解 自己的阅读进度 在接下来几天应完成多少内容
并非所有小组件家族 都适合你的小组件 从少数几个家族开始是最简单的做法
这是我正在构建的 每日阅读目标小组件 我可以使用.supportedFamilies修饰符 列出我的小组件 支持的所有家族
添加新的小组件家族时 我可以复用相同的 小组件和时间线Provider 并构建适合新家族 形状和尺寸的SwiftUI视图 我刚介绍了构建第一个小组件 所需的基本步骤 使用专注于小组件的 Widget Extension来构建小组件 你通过由时间线条目组成的时间线 向小组件提供内容 选择适合你使用场景的 时间线重新加载策略
我的App现已拥有出色的iOS小组件 帮助我把握读书俱乐部的进度 我的小组件实际上 也在其他地方可用 我的iOS小组件在CarPlay上可用 也可作为macOS上的远程小组件 我们刚介绍了构建小组件所需做的事情 接下来讨论WidgetKit提供的更多选项 以便更好地整合 你的App内容与小组件
你可以通过Deep Link 将小组件与App更好地整合 使小组件可配置 以及为小组件添加交互元素 小组件的默认交互是打开App 这是一个很好的起点 如果小组件显示App中的特定内容 小组件可以提供Deep Link 直接跳转到该内容 让我们回到 阅读目标小组件的代码 了解如何实现 阅读目标小组件 显示我正在阅读的书籍 我将提供一个Deep Link到书籍详情页 这样点击小组件 就能直接跳转到用户预期的位置 我将使用widgetURL修饰符 为App指定Deep Link URL 以便在启动时处理 该URL编码了书籍ID 让App直接跳转 到书籍详情页
Deep Link是将小组件 与App整合的绝佳方式 在用户切换时保持情境连贯 可配置小组件是将小组件 与App整合的另一种绝佳方式 使小组件可配置 让用户个性化定制小组件 使用App中的特定内容
天气小组件是一个很好的例子 用户可以配置此小组件 来显示天气状况 来自他们关心的位置 我喜欢关注优胜美地的天气 以便随时规划 一次说走就走的旅行
读书俱乐部App中的阅读日志小组件 是可配置小组件的另一个例子 使用我App的用户 可以配置此小组件来追踪 特定书籍的阅读记录 可配置小组件可以在 其放置的任何位置进行配置 例如从iOS主屏幕 这是我阅读日志小组件的配置界面 只有一个参数 该界面让用户选择 他们想要追踪的书籍 我将最近阅读的书设为默认选项 这样用户无需手动选择 可配置小组件还允许用户添加 具有不同配置的多个小组件 我的主屏幕有三个阅读日志小组件 分别追踪我正在阅读的三本书 在考虑可配置小组件时 有几点需要牢记 考虑小组件内容是否应 根据使用者的不同而变化 保持配置简洁— 通常一两个参数就足够了 不要预先要求配置 提供合理的默认值 让用户日后可以按需调整 要了解更多使用App Intents 使小组件可配置的内容 请观看 WWDC23的讲座 《Explore enhancements to App Intents》
可配置小组件让用户有更多选择 个性化设备 使设备更贴近个人需求 小组件还可以通过交互元素 与你的App整合 按钮和开关让用户 可以直接在小组件中执行操作 提醒事项是交互式小组件的绝佳示例 完成一项任务后 我可以直接在小组件中将其标记完成 获得那份继续一天所需的成就感 在考虑交互元素时 有几点需要牢记 小组件可以将交互元素 呈现为开关或按钮 思考App中最重要的操作 并在小组件中展示出来 例如阅读日志小组件 上的完成章节按钮 小组件视图由系统归档和渲染 因此代码并非一直运行 当小组件显示在屏幕上时 按钮和开关使用App Intent 系统可代你执行 当用户与元素交互时 要了解更多关于交互式小组件的内容 请观看WWDC23的 《Bring widgets to life》 小组件是你App的延伸 Deep Link 可配置小组件和交互元素 都是很好的方式 统一App与小组件之间的体验 使用你小组件的用户 还可以自定义系统体验 小组件的设计考虑了 对这些系统变化的适应能力 在iOS上 系统可以 自定义为特定颜色的着色 或透明着色 使用任一自定义选项时 系统都会通过玻璃材质渲染小组件 为内容添加色调并 用自适应玻璃效果替换背景 这让主屏幕上的每个小组件 都保持视觉统一 SwiftUI完成了大量繁重工作 但测试仍然很重要 测试小组件以确保外观出色
在我开始测试阅读目标小组件时发现 全彩色渲染效果非常出色 完成前 我想通过着色自定义 来测试并确认 小组件渲染结果符合预期 我将在设备上运行 并自定义主屏幕来测试 当我将iPhone切换到透明模式时 小组件的渲染效果不正确 书籍封面显示为一个大白色矩形 这不是预期效果 这是我的BookCoverImage视图 在视图的body中 图像针对这本书进行渲染 来自资源目录 在着色渲染模式下 系统无法 适当地为图像添加着色 我可以为此图像 指定要使用的渲染模式 使用widgetAccentedRenderingMode修饰符 有了这个选项我的小组件 现在以全彩色渲染书籍封面 我使用全彩色选项 以便书籍封面渲染时 保持原有颜色不变 经过此更改 我的小组件 在着色渲染模式下效果出色 务必在每种显示环境中测试小组件 从本地设备开始 在全彩色 着色模式下测试小组件 以及透明模式 以确保渲染符合预期 请记住 你的iOS小组件也会 以远程小组件的形式出现在macOS上 请花时间测试交互效果 在Mac上是否仍然流畅 当用户在Mac上使用时 借助SwiftUI预览快速迭代
在Xcode右侧的SwiftUI画布中 你可以检查不同家族 配色方案和渲染模式 无需离开Xcode 在测试时开启 WidgetKit开发者模式 以解除重新加载预算等限制 让你能更快地迭代小组件 要了解更多关于小组件 适应系统自定义的内容 请观看WWDC25的 《What's new in widgets》
今天我介绍了很多内容 在结束之前 请记住以下几点 希望你受到启发 去构建出色的小组件! 考虑如何扩展和 个性化你的小组件体验 通过与App整合 务必测试并让小组件 适应所有自定义环境 每个平台提供的环境 小组件让我的iPhone 充满个人特色和趣味 我迫不及待想使用 你创建的出色小组件!
-
-
3:50 - DailyReadingGoalWidget
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } } } } -
12:25 - Supported Families
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } } .supportedFamilies([.systemMedium]) } } -
14:03 - Adding deep links
struct DailyReadingGoalWidget: Widget { let kind = "DailyReadingGoalWidget" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: DailyReadingGoalProvider() ) { entry in DailyReadingGoalView(book: entry.book, message: entry.message, timeOfDay: entry.timeOfDay) .environment(\.colorScheme, .dark) .containerBackground(for: .widget) { Background() } .widgetURL(URL(string: "bookclub://reading/\(book.bookID)")) } .supportedFamilies([.systemMedium]) } } -
18:17 - Accented rendering mode
struct BookCoverImage: View { let imageName: String var body: some View { Image(imageName: bundle: .main) .widgetAccentedRenderingMode(.fullColor) } }
-
-
- 0:01 - Introduction
Widgets highlight your app's most important content across the system. The best widgets are glanceable, relevant, and personalizable. Learn how to build your first widget and keep it up to date, extending the reach of your app across platforms with WidgetKit and SwiftUI.
- 1:03 - Fundamentals
Widgets should be glanceable, relevant, and personalizable. They are built by creating a widget extension that exposes a timeline of TimelineEntry values. Each TimelineEntry provides the data to render a SwiftUI view at a particular moment in time. Learn how to define a widget with a StaticConfiguration or AppIntentConfiguration, build a quality TimelineProvider, and select a timeline reload policy to keep your widget up to date. Discover the various sizes and placements for widgets with .supportedFamilies — including the new systemExtraLargePortrait family coming to macOS, iOS, and iPadOS 27.
- 13:15 - Integrate with your app
WidgetKit offers three key integration points to tighten the connection between a widget and your app. Deep links route taps directly to specific content in your app. Configurable widgets let people personalize widget content. Interactive elements that let people perform the most important actions from within your app using App Intents.
- 17:04 - Adapt with the system
Widgets are dynamic and adapt with the system appearance modes like full color, tinted, and clear. SwiftUI handles most of the adaptation automatically, though you can customize the behavior of particular Views with the .widgetAccentedRenderingMode(.fullColor) modifier. Learn techniques to test your widgets for considerations with appearance modes and budgeted reloads.