5

widgetPerformUpdateWithCompletionHandler()让我们可以让通知中心知道 Today Extension 的内容是否发生了变化。例如:

func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
    // Refresh the widget's contents in preparation for a snapshot.
    // Call the completion handler block after the widget's contents have been
    // refreshed. Pass NCUpdateResultNoData to indicate that nothing has changed
    // or NCUpdateResultNewData to indicate that there is new data since the
    // last invocation of this method.

    if(has_new_data()) { // There was an update
        completionHandler(.NewData)
    }
    else { // nothing changed!
        completionHandler(.NoData)
    }
}

但是我们怎么知道内容是否发生了变化呢?在每个快照上,小部件都是从头开始实例化的。这是一个具有新PID的全新进程。所以你不能在你的实例中存储任何属性。如何将当前小部件内容与前一个快照的内容进行比较?

我使用 Core Data 来存储当前内容以供以后比较。这很明显并且有效。但随后另一个问题崩溃了:如果没有以前的快照怎么办?假设用户删除了小部件只是为了重新添加它。或者用户重新启动。可能还有更多原因导致我现在想不到以前没有快照。无论哪种方式 - Core Data 中仍然存储有内容。如果此旧内容与当前内容之间的比较检测到没有更改并且我返回.NoData的小部件最终会为空,因为通知中心不会重绘内容。

您可能想知道为什么以正确的状态调用 对我来说如此重要,completionHandler而不是简单地总是 return .NewData。那是因为当没有变化并且仍然返回时我正在经历一点闪烁.NewData。我的小部件中有一些图像,当重绘小部件时,整个内容会在一毫秒内不可见 - 足以引起注意。

有什么我想念的吗?对我来说很奇怪的是,Apple 提供了一种方法,让我们可以选择以不同的状态进行响应,但却无法检测到我们应该响应哪个状态。

4

4 回答 4

5

理论上,您将使用它来检查自上次调用以来是否有任何新内容,并传回适当的值。我想理论上它可能是多次调用的视图控制器的同一个实例,但这显然不是现在的工作方式。检查内容是否已更改取决于应用程序和扩展程序的性质。由于您使用共享核心数据存储来共享数据,您可能会执行以下操作:

  1. 每当应用程序更改数据时,将当前日期保存在共享用户默认值中。
  2. 任何时候今天的扩展读取数据,将当前日期保存在共享用户默认值中。
  3. widgetPerformUpdateWithCompletionHandler被调用时,查找这两个日期。如果数据比上次扩展读取该数据的时间更近,则返回NCUpdateResultNewData。如果没有,返回NCUpdateResultNoData

您还可以将这些日期保存在持久存储的元数据中,而不是共享用户默认值中。如果每次都是同一个视图控制器,您可以将步骤 2 中的值保留在内存中,而不是将其保存到文件中。但同样,这不是它现在的工作方式,也不清楚何时或是否会改变。

以其他方式保存数据的应用程序可能需要使用不同的检查,其详细信息将取决于其应用程序和扩展程序的工作方式。

在 iOS 8.2 的实践中,这真的无关紧要,因为扩展环境似乎并不关心你发回的值。我尝试返回NCUpdateResultNewData并将其与NCUpdateResultNoData每次返回进行比较。对今天的扩展视图控制器或其视图的生命周期绝对没有影响。

顺便说一句,这并不总是一个不同的过程。我试着把这条线放在今天的扩展中viewDidLoad

NSLog(@"viewDidLoad pid=%d self=%@", getpid(), self);

然后我运行了今天的扩展,在通知中心上下滚动了几次,关闭通知中心,重新打开它,得到以下信息:

2015-03-17 15:19:01.203 DemoToday[3484:903442] viewDidLoad pid=3484 self=<TodayViewController: 0x12d508cc0>
2015-03-17 15:19:14.441 DemoToday[3484:903442] viewDidLoad pid=3484 self=<TodayViewController: 0x12d50ade0>
2015-03-17 15:19:23.784 DemoToday[3484:903442] viewDidLoad pid=3484 self=<TodayViewController: 0x12d619c40>
2015-03-17 15:19:29.015 DemoToday[3484:903442] viewDidLoad pid=3484 self=<TodayViewController: 0x12d50abe0>

尽管每次都是不同的视图控制器实例,但在每种情况下它都是相同的 pid。

于 2015-03-17T21:34:11.543 回答
2

只要您启用与主机应用程序的数据共享,您就可以使用任何存储 API(Core Data、NSCoding、SQLite)。

https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html

即使用户删除了小部件或重新启动设备,数据也会存储在您的共享容器中。每当 iOS 启动您的小部件时,您将能够读取和写入您之前使用过的同一个共享容器。

请注意,您的主机应用程序和您的小部件可能会同时读取或写入,因此您必须协调任何读取和写入操作。

于 2015-03-14T14:55:29.097 回答
1

最简单的方法是将当前小部件状态缓存在用户默认值中。然后,当小部件加载时(在 viewDidLoad 中),从用户默认值中获取缓存数据并将其设置为小部件视图控制器上的属性。

然后当 widgetPerformUpdateWithCompletionHandler 被调用时,你有你以前的更新,你可以决定你是否需要做一个网络请求你的数据是否足够新鲜。如果您发出网络请求,您可以将其与缓存进行比较,以确定您是否有新数据或没有更新。

于 2015-03-16T22:01:33.277 回答
0

无需在 UserDefaults 甚至核心存储中保留任何数据。保持简单:只需声明用于存储任何计算内容或数据的变量,以检查某些内容是否更改为静态。由于小部件在同一进程中运行(如此处所示,因此下次激活小部件时静态数据将可用。

    static NSDate *lastDate;

    - (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
        NSDate * currentDate = [NSDate date];
        if (!lastDate ||
            [[NSCalendar currentCalendar]compareDate:lastDate toDate:currentDate toUnitGranularity:NSCalendarUnitDay] != NSOrderedSame) {
            // Date changed 
            lastDate = currentDate;
            ... do whatever needs to be done ...
            completionHandler(NCUpdateResultNewData);
        } else {
            completionHandler(NCUpdateResultNoData);
        }
   }
于 2016-08-18T08:55:27.493 回答