OS X Today小部件 - 如果内容发生了变化,如何在NCWidgetProviding.widgetPerformUpdateWithCompletionHandler中进行检测?

时间:2015-01-27 03:47:15

标签: xcode macos cocoa swift today-extension

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

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而不是简单地总是返回.NewData对我这么重要。那是因为我没有任何变化时会遇到一点闪烁而仍然会返回.NewData。我在我的小部件中有一些图像,当重新绘制小部件时,整个内容在一毫秒内变得不可见 - 足够长以至于注意到。

我有什么遗失的吗?我觉得很奇怪,Apple提供了一种方法,让我们可以选择不同的状态,但是却无法检测出我们应该响应的状态。

4 个答案:

答案 0 :(得分:5)

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

  1. 只要应用更改数据,请将当前日期保存为共享用户默认值。
  2. 每天今天的扩展程序读取数据时,请将当前日期保存为共享用户默认值。
  3. 调用widgetPerformUpdateWithCompletionHandler时,请查看这两个日期。如果数据的更新时间比最后一次扩展读取该数据的数据更新,请返回NCUpdateResultNewData。如果没有,请返回NCUpdateResultNoData
  4. 您还可以将这些日期保存在持久性商店的元数据中,而不是共享用户默认值中。 如果每次都是相同的视图控制器,您可以将步骤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。

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:1)

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

然后,当调用widgetPerformUpdateWithCompletionHandler时,您之前已更新,并且您可以决定是否需要对数据是否足够新的网络请求。如果您执行Web请求,则可以将其与缓存进行比较,以确定您是否有新数据或没有更新。

答案 3 :(得分:0)

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

    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);
        }
   }