Widget 学习 Keeping a Widget Up to Date

Keeping a Widget Up To Date (使小部件保持最新)

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

总览

小部件使用SwiftUI视图显示其内容。WidgetKit在一个单独的过程中代表您呈现视图。结果,即使窗口小部件在屏幕上,窗口小部件扩展也不会持续处于活动状态。尽管您的窗口小部件并非总是处于活动状态,但是有几种方法可以使它的内容保持最新。

生成可预测事件的时间表

许多小部件具有可预测的时间点,在这些时间点更新其内容是有意义的。
例如,
1.显示天气信息的小部件可能会在一整天内每小时更新一次温度。
2.股市窗口小部件可以在公开市场时间频繁更新其内容,但周末则不能完全更新。
通过提前计划这些时间,WidgetKit会在适当的时间到来时自动刷新您的窗口小部件。

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

以下是显示角色健康状况的游戏小部件的示例。
当健康水平低于100%时,角色的恢复速度为每小时25%。
例如,当角色的健康等级为25%时,需要3个小时才能完全恢复到100%。
下图显示了WidgetKit如何从提供程序请求时间轴,并在时间轴条目中指定的每个时间渲染窗口小部件。

当WidgetKit最初请求时间轴时,提供程序创建一个包含四个条目的时间轴。
第一个条目表示当前时间,后面是每小时的三个条目。
由于刷新策略设置为默认的atEnd, WidgetKit在时间轴条目中的最后日期之后请求一个新的时间轴。
当时间轴中的每个日期到达时,WidgetKit将调用小部件的内容闭包并显示结果。
最后一个时间线条目通过后,WidgetKit通过向提供者请求新的时间线重复该过程。
因为角色的运行状况已经达到100%,提供者会响应一个当前时间的单一条目,并将刷新策略设置为never。通过这个设置,WidgetKit不会要求另一个时间轴,直到应用程序使用WidgetCenter告诉WidgetKit请求一个新的时间轴。

除了atEnd和never refresh策略之外,如果时间线在到达条目末尾之前或之后可能发生更改,提供程序还可以完全指定不同的日期。
例如,如果一条龙将在2小时内出现,向角色挑战战斗,提供程序将重载策略设置为after(_:),将日期在2小时后传递。
下图显示了WidgetKit在呈现小部件2小时后如何请求一个新部件。

由于与龙的战斗,角色的治疗将需要额外2小时达到100%。新的时间轴包含两个条目,一个是当前时间,另一个是未来2小时。时间轴为刷新策略指定atEnd,表示没有其他已知事件可能改变时间轴。

当两个小时过去,并且角色的健康达到100%时,WidgetKit询问提供者一个新的时间表。
因为角色的健康状况已经恢复,提供者生成与上面第一个图表相同的最终时间线。
当用户玩游戏时,角色的健康水平发生变化时,应用程序会使用WidgetCenter让WidgetKit刷新时间轴并更新小部件。

除了指定时间线结束之前的日期外,提供程序还可以指定时间线结束之后的日期。
当您知道小部件的状态直到稍后才会改变时,这是非常有用的。
例如,股票市场小部件可以在周五市场收盘时创建时间轴,使用afterDate()刷新策略指定周一市场开盘时间。因为股市在周末关闭,所以在股市开盘前没有必要更新widget。

时间线更改时通知WidgetKit

您的应用程序可以告诉WidgetKit,当有事情影响到widget的当前时间轴时,请求一个新的时间轴。
在上面的游戏小部件的例子中,如果应用程序收到一个推送通知,表明一个队友给了角色一剂治疗药水,应用程序可以告诉WidgetKit重新加载时间轴并更新小部件的内容。
要重新加载特定类型的小部件,你的应用程序使用WidgetCenter,如下所示:

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

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

如果小部件具有用户可配置的属性,请使用WidgetCenter来验证是否存在具有适当设置的小部件,从而避免不必要的重新加载。
例如,当游戏收到一个关于一个角色收到治疗药水的推送通知时,它会在重新加载时间线之前验证一个小部件是否显示了这个角色。

在下面的代码中,应用程序调用getCurrentConfigurations(_:)来检索用户配置的小部件列表。
然后迭代得到的WidgetInfo对象,找到一个意图与接受治疗药水的角色配置的对象。
如果找到一个,应用程序就会调用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)
    }
}

如果你的应用程序使用WidgetBundle来支持多个小部件,你可以使用WidgetCenter来重新加载所有小部件的时间线。
例如,如果你的小部件需要用户登录到一个帐户,但他们已经注销,你可以通过调用重新加载所有小部件:

WidgetCenter.shared.reloadAllTimelines()

显示动态日期

由于您的窗口小部件扩展程序并非始终运行,因此您无法直接更新窗口小部件的内容。
而是,WidgetKit代表您渲染窗口小部件的视图并显示结果。
但是,某些SwiftUI视图可让您显示在可见窗口小部件时继续更新的内容。

使用Text小部件中的视图,您可以在屏幕上显示最新的日期和时间。以下示例显示了可用的组合。

要显示自动更新的相对时间:

let components = DateComponents(minute: 11, second: 14)
let futureDate = Calendar.current.date(byAdding: components, to: Date())!

Text(futureDate, style: .relative)
// Displays:
// 11 min, 14 sec

Text(futureDate, style: .offset)
// Displays:
// -11 minutes

使用相对样式显示当前日期和时间与指定日期之间的绝对差异,而不管该日期是将来的还是过去的。
偏移样式显示当前日期和时间与指定日期之间的差异,表示未来的日期使用减号(-)前缀,表示过去的日期使用加号(+)前缀。

要显示继续自动更新的计时器:

let components = DateComponents(minute: 15)
let futureDate = Calendar.current.date(byAdding: components, to: Date())!

Text(futureDate, style: .timer)
// Displays:
// 15:00

对于将来的日期,timer样式将递减计数,直到当前时间达到指定的日期和时间为止,并在日期经过时递增计数。

要显示绝对日期或时间:

// Absolute Date or Time
let components = DateComponents(year: 2020, month: 4, day: 1, hour: 9, minute: 41)
let aprilFirstDate = Calendar.current(components)!

Text(aprilFirstDate, style: .date)
Text("Date: \(aprilFirstDate, style: .date)")
Text("Time: \(aprilFirstDate, style: .time)")

// Displays:
// April 1, 2020
// Date: April 1, 2020
// Time: 9:41AM

最后,显示两个日期之间的时间间隔:

let startComponents = DateComponents(hour: 9, minute: 30)
let startDate = Calendar.current.date(from: startComponents)!

let endComponents = DateComponents(hour: 14, minute: 45)
let endDate = Calendar.current.date(from: endComponents)!

Text(startDate ... endDate)
Text("The meeting will take place: \(startDate ... endDate)")

// Displays:
// 9:30AM-2:45PM
// The meeting will take place: 9:30AM-2:45PM

后台网络请求完成后更新

当您的小部件扩展处于活动状态时,比如在提供快照或时间轴时,它可以发起后台网络请求。
这个过程类似于应用程序处理这种类型的请求,这在后台下载文件中有描述。
WidgetKit不是恢复你的应用程序,而是直接激活widget的扩展。
要处理网络请求的结果,使用onBackgroundURLSessionEvents(matching:_:)修饰符到你的小部件的配置,并执行以下操作:

  • 存储对completion参数的引用。您在处理所有网络事件后调用完成处理程序。
  • 使用identifier参数查找URLSession启动后台请求时使用的对象。
    如果您的窗口小部件扩展名已终止,请使用标识符重新创建URLSession。

在调用onBackgroundURLSessionEvents()后,系统调用您提供给urlSession的URLSessionDelegate的urlSession(_:downloadTask:didFinishDownloadingTo:)方法。
当所有的事件都被传递后,系统调用委托的urlSessionDidFinishEvents(forBackgroundURLSession:)方法。

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

另外阅读

时间线管理

protocol TimelineProvider

建议WidgetKit何时更新窗口小部件显示的类型。

protocol IntentTimelinProvider

建议WidgetKit何时更新用户可配置的窗口小部件显示的类型。

struct TimelineProviderContext

一个对象,其中包含有关如何渲染小部件的详细信息,包括其大小以及它是否出现在小部件库中。

protocol TimelineEntry

一种类型,指定显示窗口小部件的日期,并可选地指示窗口小部件内容的当前相关性。

struct Timeline

一个对象,指定WidgetKit更新窗口小部件视图的日期。

class WidgetCenter

包含用户配置的小部件列表的对象,用于重新加载小部件时间线。

你可能感兴趣的:(Widget 学习 Keeping a Widget Up to Date)