protocol TimelineProvider
protocol IntentTimelineProvider
struct TimelineProviderContext
protocol TimelineEntry
struct Timeline
class WidgetCenter
小组件使用 SwiftUI 视图来显示它们的内容。WidgetKit 在单独的进程中代你渲染视图。因此,即使你的小组件在屏幕上,小组件扩展也不会持续处于活跃状态。尽管你的小组件并不总是处于活跃状态,但你可以通过多种方式来使其内容保持最新。
重新载入小组件会占用系统资源,并且由于涉及到额外的联网和处理会导致电池耗尽。为减少这一性能影响并保持全日的电池续航,请将你请求的更新频率和次数限制在必要的范围内。
为管理系统负载,WidgetKit 使用预算来分配小组件在一天当中的重新载入。预算分配是动态的,会将多个因素考虑在内,其中包括:
小组件向用户进行显示的频率和次数。
小组件的上次重新载入时间。
小组件的包含 App 是否处于活跃状态。
WidgetKit 为用户在其设备上添加的每个活跃小组件维护不同的预算。例如,如果用户添加了某个可配置体育小组件的两个实例,用于显示两支不同队伍的信息,则每个小组件都有自己的预算。
小组件预算的适用周期为 24 小时。WidgetKit 会根据用户的日常使用模式调整 24 小时窗口,这意味着每日预算不一定会在午夜重置。对于用户经常查看的小组件,每日预算通常包括 40 到 70 次刷新。这一速率可以大致换算为每 15 到 60 分钟重新载入一次小组件,但是由于涉及到多个因素,这些时间间隔通常会有所不同。
注释
系统需要用几天时间来学习用户的行为。在这一学习期内,你的小组件可能会获得比正常情况下要多的重新载入。
在下列情况中,WidgetKit 不会将重新载入次数计入小组件的预算:
小组件的包含 App 在前台。
小组件的包含 App 有活跃的音频或导航会话。
系统语言区设置发生更改。
动态类型或辅助功能设置发生更改。
对于系统外观更改或系统语言区设置更改等情况,不要从你的 App 请求时间线重新载入。系统会自动更新你的小组件。
你的重新载入时间表由小组件时间线提供程序推动,但有时 WidgetKit 会为了帮助使小组件内容保持最新而重新载入它。一些常见情景包括:
如果某个小组件在主屏幕页面上而用户很少访问,则 WidgetKit 可能会降低重新载入这个小组件的频率。然后,当用户查看该页面时,WidgetKit 可以在小组件可见时重新载入。
对于使用定位服务的小组件,WidgetKit 会在发生重大位置更改时重新载入。有关重新载入使用定位服务的小组件的更多信息,请参阅“访问小组件中的位置信息”。
如果你的小组件可以预测它应进行重新载入的时间点,最好的方法是为尽可能多的未来日期生成时间线。确保时间线中的条目间隔对你显示的内容来说尽可能大。WidgetKit 在它重新载入小组件之前施加了一小段时间。你的时间线提供程序在创建时间线条目时,应使各条目至少间隔 5 分钟。WidgetKit 可能会跨多个小组件合并进行重新载入,从而影响重新载入某个小组件的确切时间。
许多小组件都有可预测的时间点,在这些时间点更新内容是比较合理的做法。例如,显示天气信息的小组件可能会在一天中每小时更新一次温度。股市小组件会在开盘时段内频繁更新其内容,而在周末则完全不会进行更新。通过提前计划这些时间点,WidgetKit 会在适当的时间到来时自动刷新你的小组件。
在你定义小组件时,你会实现一个自定 Timeline
。WidgetKit 从你的提供程序那里获取一个时间线,并使用它来跟踪何时更新你的小组件。时间线是一组 Timeline
对象。时间线中的每个条目都有一个日期和时间,以及小组件在显示其视图时所需的附加信息。除了时间线条目之外,时间线还指定了一个刷新策略,用来告诉 WidgetKit 何时应请求新的时间线。
以下是一个用于显示角色生命值等级的游戏小组件的示例。当生命值等级低于 100% 时,角色会以每小时 25% 的速率进行恢复。例如,当角色的生命值等级为 25% 时,需要 3 个小时才能完全恢复到 100%。下图显示了 WidgetKit 如何从提供程序请求时间线,从而在时间线条目中指定的每个时间呈现小组件。
当 WidgetKit 最初请求时间线时,提供程序会创建一个含有四个条目的时间线。第一个条目表示当前时间,后面三个条目各间隔一小时。在将刷新策略设置为默认值 at
时,WidgetKit 会在时间线条目中最后一个日期之后请求一个新的时间线。当时间线中的每个日期都已到达时,WidgetKit 会调用小组件的内容闭包并显示结果。在过了最后一个时间线条目之后,WidgetKit 会向提供程序请求一个新的时间线以重复这一过程。由于角色的生命值已达到 100%,因此提供程序会以当前时间的单个条目进行响应,并将刷新策略设置为 never
。采用这项设置后,在 App 使用 Widget
告诉 WidgetKit 请求一个新的时间线之前,WidgetKit 不会请求其他时间线。
除了 at
和 never
刷新策略外,如果时间线在到达最后条目之前或之后发生变化,提供程序还可以指定一个不同的日期。例如,如果一条龙将在 2 小时后出现并向角色挑起战斗,则提供程序会将重新载入策略设置为 after(_:)
,传递 2 小时后的日期。下图显示了 WidgetKit 如何在 2 小时标记处呈现小组件后请求一个新的时间线。
由于与龙进行战斗,角色的生命值要恢复到 100% 将额外需要 2 个小时。新的时间线包含两个条目,一个条目对应当前时间,另一个条目对应 2 小时后。时间线指定 at
作为刷新策略,指示没有更多会改变时间线的已知事件。
当 2 小时已过,且角色的生命值达到 100% 时,WidgetKit 会向提供程序请求一个新的时间线。由于角色的生命值已恢复,因此提供程序生成的最终时间线与上面的第一个示意图相同。当用户玩游戏且角色的生命值等级发生变化时,App 会使用 Widget
让 WidgetKit 刷新时间线并更新小组件。
除了指定早于时间线最后的日期外,提供程序还可以指定晚于时间线最后的日期。当你知道小组件的状态在将来某个时间之前不会改变时,这会很有用。例如,股市小组件可以在周五收盘时创建一个刷新策略为 after
的时间线,用于指定周一开盘的时间。由于股市周末休市,因此在开盘前无需更新小组件。
重要信息
如果你的小组件在重新载入时会向服务器发送请求,请提前计划,并在时间线条目中使用 after
提供特定的日期。WidgetKit 会尝试遵守你指定的日期,这样一来,当多台设备在大致相同的时间重新载入你的小组件时,可能会导致服务器负载显著增加。
当某种情况影响到小组件的当前时间线时,你的 App 可以指示 WidgetKit 请求一个新的时间线。在上面的游戏小组件示例中,如果 App 收到一条推送通知,说明队友已为角色提供了治疗药水,则 App 可以指示 WidgetKit 重新载入时间线并更新小组件的内容。为重新载入特定类型的小组件,你的 App 会使用 Widget
,如下所示:
WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")
kind
参数包含的字符串与用于创建小组件的 Widget
的值相同。
如果你的小组件具有用户可配置的属性,请使用 WidgetCenter 验证具有相应设置的小组件是否已存在,从而避免不必要的重新载入。例如,当游戏收到关于角色获得治疗药水的推送通知时,它会在重新载入时间线之前验证小组件是否正在显示该角色。
在下面的代码中,App 调用 get
来检索用户配置的小组件的列表。然后,它会遍历生成的 Widget
对象,以找到其中的 intent
配置为角色已获得治疗药水的对象。如果找到相应对象,App 将为该小组件的 kind
调用 reload
。
WidgetCenter.shared.getCurrentConfigurations { result in
guard case .success(let widgets) = result else { return }
// Iterate over the WidgetInfo elements to find one that matches
// the character from the push notification.
if let widget = widgets.first(
where: { widget in
let intent = widget.configuration as? SelectCharacterIntent
return intent?.character == characterThatReceivedHealingPotion
}
) {
WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
}
}
如果你的 App 使用 Widget
支持多个小组件,你可以使用 Widget
为你的所有小组件重新载入时间线。例如,如果你的小组件要求用户登录账户,但他们已退出登录,你可以通过调用以下命令重新载入所有小组件:
WidgetCenter.shared.reloadAllTimelines()
即使你的小组件不会持续运行,它也可以显示 WidgetKit 实时更新的基于时间的信息。例如,它可能会显示一个倒计时器,即使你的小组件扩展未在运行,该倒计时器也会继续进行倒计时。有关更多信息,请参阅“在小组件中显示动态日期”。
当你的小组件扩展处于活跃状态时,例如,提供 snapshot
或 timeline
时,它可以发起后台网络请求。例如,获取队友当前状态的游戏小组件,或获取标题带有缩略图的新闻小组件。发出异步后台网络请求可让你快速将控制权交还给系统,从而降低因响应时间过长而被终止的风险。
这个过程与 App 处理这类请求的方式类似,如“在后台下载文件”中所述。WidgetKit 不会重新开启你的 App,而是直接激活你的小组件扩展。为处理网络请求的结果,请在小组件的配置中使用 on
修饰符,并执行以下操作:
存储对 completion
参数的引用。在处理所有网络事件后调用完成处理程序。
使用 identifier
参数查找在发起后台请求时使用的 URLSession
对象。如果你的小组件扩展已终止,请使用这个标识符重新创建 URLSession
。
在调用 on
之后,系统会调用你向 URLSession
提供的 URLSession
的 url
方法。当所有事件都已提交后,系统调用这一委托的 url
方法。
要在网络请求完成后刷新小组件的时间线,请从你的委托的 url
实现中调用 Widget
方法。在你完成事件处理后,调用之前存储在 on
中的 completion
处理程序。
protocol TimelineProvider
protocol IntentTimelineProvider
struct TimelineProviderContext
protocol TimelineEntry
struct Timeline
class WidgetCenter