文章

让小组件保持最新状态

计划小组件的时间线,以使用动态视图及时显示相关信息,并在情况发生变化时更新时间线。

最新英文文章

Keeping a Widget Up To Date

概览

小组件使用 SwiftUI 视图来显示它们的内容。WidgetKit 在单独的进程中代你渲染视图。因此,即使你的小组件在屏幕上,小组件扩展也不会持续处于活跃状态。尽管你的小组件并不总是处于活跃状态,但你可以通过多种方式来使其内容保持最新。

在预算内计划重新载入

重新载入小组件会占用系统资源,并且由于涉及到额外的联网和处理会导致电池耗尽。为减少这一性能影响并保持全日的电池续航,请将你请求的更新频率和次数限制在必要的范围内。

为管理系统负载,WidgetKit 使用预算来分配小组件在一天当中的重新载入。预算分配是动态的,会将多个因素考虑在内,其中包括:

  • 小组件向用户进行显示的频率和次数。

  • 小组件的上次重新载入时间。

  • 小组件的包含 App 是否处于活跃状态。

WidgetKit 为用户在其设备上添加的每个活跃小组件维护不同的预算。例如,如果用户添加了某个可配置体育小组件的两个实例,用于显示两支不同队伍的信息,则每个小组件都有自己的预算。

小组件预算的适用周期为 24 小时。WidgetKit 会根据用户的日常使用模式调整 24 小时窗口,这意味着每日预算不一定会在午夜重置。对于用户经常查看的小组件,每日预算通常包括 40 到 70 次刷新。这一速率可以大致换算为每 15 到 60 分钟重新载入一次小组件,但是由于涉及到多个因素,这些时间间隔通常会有所不同。

在下列情况中,WidgetKit 不会将重新载入次数计入小组件的预算:

  • 小组件的包含 App 在前台。

  • 小组件的包含 App 有活跃的音频或导航会话。

  • 系统语言区设置发生更改。

  • 动态类型或辅助功能设置发生更改。

对于系统外观更改或系统语言区设置更改等情况,不要从你的 App 请求时间线重新载入。系统会自动更新你的小组件。

你的重新载入时间表由小组件时间线提供程序推动,但有时 WidgetKit 会为了帮助使小组件内容保持最新而重新载入它。一些常见情景包括:

  • 如果某个小组件在主屏幕页面上而用户很少访问,则 WidgetKit 可能会降低重新载入这个小组件的频率。然后,当用户查看该页面时,WidgetKit 可以在小组件可见时重新载入。

  • 对于使用定位服务的小组件,WidgetKit 会在发生重大位置更改时重新载入。有关重新载入使用定位服务的小组件的更多信息,请参阅“访问小组件中的位置信息”。

如果你的小组件可以预测它应进行重新载入的时间点,最好的方法是为尽可能多的未来日期生成时间线。确保时间线中的条目间隔对你显示的内容来说尽可能大。WidgetKit 在它重新载入小组件之前施加了一小段时间。你的时间线提供程序在创建时间线条目时,应使各条目至少间隔 5 分钟。WidgetKit 可能会跨多个小组件合并进行重新载入,从而影响重新载入某个小组件的确切时间。

为可预测的事件生成时间线

许多小组件都有可预测的时间点,在这些时间点更新内容是比较合理的做法。例如,显示天气信息的小组件可能会在一天中每小时更新一次温度。股市小组件会在开盘时段内频繁更新其内容,而在周末则完全不会进行更新。通过提前计划这些时间点,WidgetKit 会在适当的时间到来时自动刷新你的小组件。

在你定义小组件时,你会实现一个自定 TimelineProvider。WidgetKit 从你的提供程序那里获取一个时间线,并使用它来跟踪何时更新你的小组件。时间线是一组 TimelineEntry 对象。时间线中的每个条目都有一个日期和时间,以及小组件在显示其视图时所需的附加信息。除了时间线条目之外,时间线还指定了一个刷新策略,用来告诉 WidgetKit 何时应请求新的时间线。

以下是一个用于显示角色生命值等级的游戏小组件的示例。当生命值等级低于 100% 时,角色会以每小时 25% 的速率进行恢复。例如,当角色的生命值等级为 25% 时,需要 3 个小时才能完全恢复到 100%。下图显示了 WidgetKit 如何从提供程序请求时间线,从而在时间线条目中指定的每个时间呈现小组件。

示意图显示了 WidgetKit 请求时间线,提供程序生成时间线,以及 WidgetKit 请求新的时间线后 3 小时内的时间历程

当 WidgetKit 最初请求时间线时,提供程序会创建一个含有四个条目的时间线。第一个条目表示当前时间,后面三个条目各间隔一小时。在将刷新策略设置为默认值 atEnd 时,WidgetKit 会在时间线条目中最后一个日期之后请求一个新的时间线。当时间线中的每个日期都已到达时,WidgetKit 会调用小组件的内容闭包并显示结果。在过了最后一个时间线条目之后,WidgetKit 会向提供程序请求一个新的时间线以重复这一过程。由于角色的生命值已达到 100%,因此提供程序会以当前时间的单个条目进行响应,并将刷新策略设置为 never。采用这项设置后,在 App 使用 WidgetCenter 告诉 WidgetKit 请求一个新的时间线之前,WidgetKit 不会请求其他时间线。

除了 atEndnever 刷新策略外,如果时间线在到达最后条目之前或之后发生变化,提供程序还可以指定一个不同的日期。例如,如果一条龙将在 2 小时后出现并向角色挑起战斗,则提供程序会将重新载入策略设置为 after(_:),传递 2 小时后的日期。下图显示了 WidgetKit 如何在 2 小时标记处呈现小组件后请求一个新的时间线。

示意图显示了 WidgetKit 请求时间线、提供程序生成时间线,以及 WidgetKit 在 2 小时后请求一个新的时间线

由于与龙进行战斗,角色的生命值要恢复到 100% 将额外需要 2 个小时。新的时间线包含两个条目,一个条目对应当前时间,另一个条目对应 2 小时后。时间线指定 atEnd 作为刷新策略,指示没有更多会改变时间线的已知事件。

当 2 小时已过,且角色的生命值达到 100% 时,WidgetKit 会向提供程序请求一个新的时间线。由于角色的生命值已恢复,因此提供程序生成的最终时间线与上面的第一个示意图相同。当用户玩游戏且角色的生命值等级发生变化时,App 会使用 WidgetCenter 让 WidgetKit 刷新时间线并更新小组件。

除了指定早于时间线最后的日期外,提供程序还可以指定晚于时间线最后的日期。当你知道小组件的状态在将来某个时间之前不会改变时,这会很有用。例如,股市小组件可以在周五收盘时创建一个刷新策略为 afterDate() 的时间线,用于指定周一开盘的时间。由于股市周末休市,因此在开盘前无需更新小组件。

在时间线发生改变时告知 WidgetKit

当某种情况影响到小组件的当前时间线时,你的 App 可以指示 WidgetKit 请求一个新的时间线。在上面的游戏小组件示例中,如果 App 收到一条推送通知,说明队友已为角色提供了治疗药水,则 App 可以指示 WidgetKit 重新载入时间线并更新小组件的内容。为重新载入特定类型的小组件,你的 App 会使用 WidgetCenter,如下所示:

WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.character-detail")
							

kind 参数包含的字符串与用于创建小组件的 WidgetConfiguration 的值相同。

如果你的小组件具有用户可配置的属性,请使用 WidgetCenter 验证具有相应设置的小组件是否已存在,从而避免不必要的重新载入。例如,当游戏收到关于角色获得治疗药水的推送通知时,它会在重新载入时间线之前验证小组件是否正在显示该角色。

在下面的代码中,App 调用 getCurrentConfigurations(_:) 来检索用户配置的小组件的列表。然后,它会遍历生成的 WidgetInfo 对象,以找到其中的 intent 配置为角色已获得治疗药水的对象。如果找到相应对象,App 将为该小组件的 kind 调用 reloadTimelines(ofKind:)


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 使用 WidgetBundle 支持多个小组件,你可以使用 WidgetCenter 为你的所有小组件重新载入时间线。例如,如果你的小组件要求用户登录账户,但他们已退出登录,你可以通过调用以下命令重新载入所有小组件:

WidgetCenter.shared.reloadAllTimelines()
							

显示动态日期

即使你的小组件不会持续运行,它也可以显示 WidgetKit 实时更新的基于时间的信息。例如,它可能会显示一个倒计时器,即使你的小组件扩展未在运行,该倒计时器也会继续进行倒计时。有关更多信息,请参阅“在小组件中显示动态日期”。

后台网络请求完成后更新

当你的小组件扩展处于活跃状态时,例如,提供 snapshottimeline 时,它可以发起后台网络请求。例如,获取队友当前状态的游戏小组件,或获取标题带有缩略图的新闻小组件。发出异步后台网络请求可让你快速将控制权交还给系统,从而降低因响应时间过长而被终止的风险。

这个过程与 App 处理这类请求的方式类似,如“在后台下载文件”中所述。WidgetKit 不会重新开启你的 App,而是直接激活你的小组件扩展。为处理网络请求的结果,请在小组件的配置中使用 onBackgroundURLSessionEvents(matching:_:) 修饰符,并执行以下操作:

  • 存储对 completion 参数的引用。在处理所有网络事件后调用完成处理程序。

  • 使用 identifier 参数查找在发起后台请求时使用的 URLSession 对象。如果你的小组件扩展已终止,请使用这个标识符重新创建 URLSession

在调用 onBackgroundURLSessionEvents() 之后,系统会调用你向 URLSession 提供的 URLSessionDelegateurlSession(_:downloadTask:didFinishDownloadingTo:) 方法。当所有事件都已提交后,系统调用这一委托的 urlSessionDidFinishEvents(forBackgroundURLSession:) 方法。

要在网络请求完成后刷新小组件的时间线,请从你的委托的 urlSessionDidFinishEvents 实现中调用 WidgetCenter 方法。在你完成事件处理后,调用之前存储在 onBackgroundURLSessionEvents() 中的 completion 处理程序。

另请参阅

时间线管理