如果在执行过程中没有隐藏钟面,Apple Watch ClockKit并发症不会更新他们的时间线条目

时间:2016-06-15 22:44:01

标签: ios apple-watch apple-watch-complication clockkit

是否有其他人注意到并发症条目无法正确更新的问题。我刚刚为我的应用添加了一些初步支持,但发现它们并没有显示我希望它们显示的内容。例如,为了便于快速测试这个问题,我创建了一个时间表

A -> B -> C
0s   10s  20s

然而,我所看到的是并发症入口A在B和C应该显示的时间之后。

我的普通应用程序本身并没有设置为像这样创建规则间隔的复杂功能,它有许多可以由用户设置的定时器方面,但是其中一个方面只允许用户启动多个定时器一下子,所有这些都将在用户定义的持续时间之后完成。与iOS时钟应用程序的计时器不同,您可以在几秒钟内指定计时器持续时间,因此两个计时器完全可能在几秒钟内完成,但总体来说它们更可能是#&# 39;相隔几分钟。此外,不应该添加太多复杂条目,尽管我的应用程序的其他更复杂的方面可以轻松添加10s甚至~100个复杂条目,具体取决于用户在其中设置的任务的复杂程度。但就目前而言,这个更简单的例子更容易讨论和测试。

我将Xcode更新到最新版本(7.3.2)并没有任何改进,并将构建发送到我的实际手机并观看并再次没有改进。直到它确实有效。在进一步的调试中,我发现我可以通过简单地降低我的手表(关闭屏幕)然后再次唤醒它来使时间线表现自己,同时在执行我的时间线时。完成此操作后,时间表将从此开始正常运作。

我已经创建了一个测试应用程序来演示问题,这确实可以完全重现问题,因此我将在错误报告中将其发送给Apple。只是想到我是否有人注意到这个问题。

此外,当我的测试应用程序执行时,我得到以下日志输出,但错误没有意义

-[ExtensionDelegate session:didReceiveUserInfo:]:67 - complication.family=1 in activeComplications - calling reloadTimelineForComplication
-[ComplicationController getTimelineStartDateForComplication:withHandler:]:43 - calling handler for startDate=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEndDateForComplication:withHandler:]:73 - calling handler for endDate=2016-06-15 22:08:46 +0000
-[ComplicationController getCurrentTimelineEntryForComplication:withHandler:]:148 - calling handler for entry at date=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEntriesForComplication:afterDate:limit:withHandler:]:202 - adding entry at date=2016-06-15 22:08:36 +0000; with timerEndDate=2016-06-15 22:08:46 +0000 i=1
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 2016-06-15 22:08:46 +0000).  Excess entries will be discarded.

此日志中的相关信息如下

getTimelineStartDateForComplication - calling handler for startDate=22:08:26
getTimelineEndDateForComplication - calling handler for endDate=22:08:46 
getCurrentTimelineEntryForComplication -  calling handler for entry at date=22:08:26
getTimelineEntriesForComplication:afterDate - adding entry at date=22:08:36
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 22:08:46).  Excess entries will be discarded.

你可以在系统最后看到它使用 22:08:46 的开始日期的错误,这实际上是我告诉Clockkit我的时间线' s endDate, NOT startDate。我不确定这是否与我看到的行为有关,因为我在隐藏/显示屏幕后看到相同的错误。

我已在我的在线测试应用here中放置了此行为的视频。该测试应用程序的详细信息如下

可以在相关模拟器中运行的完整代码here,此处还列出了相关的复杂功能模块以供参考。

在我的扩展委托中,我从iOS应用程序接收userInfo并安排重新加载我的并发症时间表

ExtensionDelegate.m

- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo
{
  DbgLog(@"");

  WKExtension *extension = [WKExtension sharedExtension];
  DbgLog(@"self=%p; wkExtension=%p; userInfo=%@", self, extension, userInfo);

  self.lastReceivedUserInfo = userInfo;

  CLKComplicationServer *complicationServer = [CLKComplicationServer sharedInstance];
  for (CLKComplication *complication in complicationServer.activeComplications)
  {
    DbgLog(@"complication.family=%d in activeComplications - calling reloadTimelineForComplication", complication.family);
    [complicationServer reloadTimelineForComplication:complication];
  }
}

然后在我的ComplicationController中有以下方法来处理事情的复杂性

ComplicationController.m

#define DbgLog(fmt, ...)    NSLog((@"%s:%d - " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)

@interface ComplicationController ()

@end

@implementation ComplicationController

#pragma mark - Timeline Configuration

- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler
{
  handler(CLKComplicationTimeTravelDirectionForward|CLKComplicationTimeTravelDirectionBackward);
}

- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
  NSDate *startDate;

  WKExtension *extension = [WKExtension sharedExtension];
  assert(extension.delegate);
  assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
  ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
  if (extensionDelegate.lastReceivedUserInfo)
  {
    NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
    startDate = [userInfo objectForKey:@"date"];
  }

  DbgLog(@"calling handler for startDate=%@", startDate);
  handler(startDate);
}

- (NSDate*)getTimelineEndDate
{
  NSDate *endDate;

  WKExtension *extension = [WKExtension sharedExtension];
  assert(extension.delegate);
  assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
  ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
  if (extensionDelegate.lastReceivedUserInfo)
  {
    NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
    NSDate *startDate = [userInfo objectForKey:@"date"];
    NSNumber *duration = [userInfo objectForKey:@"duration"];
    NSNumber *count = [userInfo objectForKey:@"count"];

    NSTimeInterval totalDuration = duration.floatValue * count.floatValue;
    endDate = [startDate dateByAddingTimeInterval:totalDuration];
  }

  return endDate;
}

- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
  NSDate *endDate=[self getTimelineEndDate];

  DbgLog(@"calling handler for endDate=%@", endDate);
  handler(endDate);
}

- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handler {
    handler(CLKComplicationPrivacyBehaviorShowOnLockScreen);
}

#pragma mark - Timeline Population

- (CLKComplicationTemplate *)getComplicationTemplateForComplication:(CLKComplication *)complication
                             forEndDate:(NSDate *)endDate
                             orBodyText:(NSString *)bodyText
                             withHeaderText:(NSString *)headerText
{
  assert(complication.family == CLKComplicationFamilyModularLarge);

  CLKComplicationTemplateModularLargeStandardBody *template = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];

  template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:headerText];
  if (endDate)
  {
    template.body1TextProvider = [CLKRelativeDateTextProvider textProviderWithDate:endDate style:CLKRelativeDateStyleTimer units:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond];
  }
  else
  {
    assert(bodyText);
    template.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:bodyText];
  }

  return template;
}

- (CLKComplicationTimelineEntry *)getComplicationTimelineEntryForComplication:(CLKComplication *)complication
                                 forStartDate:(NSDate *)startDate
                                      endDate:(NSDate *)endDate
                                   orBodyText:(NSString *)bodyText
                                   withHeaderText:(NSString *)headerText
{
  CLKComplicationTimelineEntry *entry = [[CLKComplicationTimelineEntry alloc] init];
  entry.date = startDate;
  entry.complicationTemplate = [self getComplicationTemplateForComplication:complication forEndDate:endDate orBodyText:bodyText withHeaderText:headerText];

  return entry;
}

- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
{
  // Call the handler with the current timeline entry
  CLKComplicationTimelineEntry *entry;
  assert(complication.family == CLKComplicationFamilyModularLarge);

  WKExtension *extension = [WKExtension sharedExtension];
  assert(extension.delegate);
  assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
  ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
  if (extensionDelegate.lastReceivedUserInfo)
  {
    NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
    NSDate *startDate = [userInfo objectForKey:@"date"];
    NSNumber *duration = [userInfo objectForKey:@"duration"];
    //NSNumber *count = [userInfo objectForKey:@"count"];

    NSTimeInterval totalDuration = duration.floatValue;
    NSDate *endDate = [startDate dateByAddingTimeInterval:totalDuration];

    entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:startDate endDate:endDate orBodyText:nil withHeaderText:@"current"];
  }

  if (!entry)
  {
    NSDate *currentDate = [NSDate date];
    entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:currentDate endDate:nil orBodyText:@"no user info" withHeaderText:@"current"];
  }

  DbgLog(@"calling handler for entry at date=%@", entry.date);
  handler(entry);
}

- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
  NSArray *retArray;
  assert(complication.family == CLKComplicationFamilyModularLarge);

  WKExtension *extension = [WKExtension sharedExtension];
  assert(extension.delegate);
  assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
  ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
  if (extensionDelegate.lastReceivedUserInfo)
  {
    NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
    NSDate *startDate = [userInfo objectForKey:@"date"];
    if ([startDate timeIntervalSinceDate:date] < 0.f)
    {
      assert(0);
      // not expected to be asked about any date earlier than our startDate
    }
  }

  // Call the handler with the timeline entries prior to the given date
  handler(retArray);
}

- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
  NSMutableArray *timelineEntries = [[NSMutableArray alloc] init];
  assert(complication.family == CLKComplicationFamilyModularLarge);

  WKExtension *extension = [WKExtension sharedExtension];
  assert(extension.delegate);
  assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
  ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
  if (extensionDelegate.lastReceivedUserInfo)
  {
    NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
    NSDate *startDate = [userInfo objectForKey:@"date"];
    NSNumber *duration = [userInfo objectForKey:@"duration"];
    NSNumber *count = [userInfo objectForKey:@"count"];

    NSInteger i;
    for (i=0; i<count.integerValue && timelineEntries.count < limit; ++i)
    {
      NSTimeInterval entryDateOffset = duration.floatValue * i;
      NSDate *entryDate = [startDate dateByAddingTimeInterval:entryDateOffset];

      if ([entryDate timeIntervalSinceDate:date] > 0)
      {
    NSDate *timerEndDate = [entryDate dateByAddingTimeInterval:duration.floatValue];

    DbgLog(@"adding entry at date=%@; with timerEndDate=%@ i=%d", entryDate, timerEndDate, i);

    CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:entryDate endDate:timerEndDate orBodyText:nil withHeaderText:[NSString stringWithFormat:@"After %d", i]];
    [timelineEntries addObject:entry];
      }
    }

    if (i==count.integerValue && timelineEntries.count < limit)
    {
      NSDate *timelineEndDate = [self getTimelineEndDate];
      CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:timelineEndDate endDate:nil orBodyText:@"Finished" withHeaderText:@"Test"];
      [timelineEntries addObject:entry];
    }
  }

  NSArray *retArray;

  if (timelineEntries.count > 0)
  {
    retArray = timelineEntries;
  }

  // Call the handler with the timeline entries after to the given date
  handler(retArray);
}

#pragma mark Update Scheduling

/*
 // don't want any updates other than the ones we request directly
- (void)getNextRequestedUpdateDateWithHandler:(void(^)(NSDate * __nullable updateDate))handler
{
  // Call the handler with the date when you would next like to be given the opportunity to update your complication content
  handler(nil);
}
*/

- (void)requestedUpdateBudgetExhausted
{
  DbgLog(@"");

}

#pragma mark - Placeholder Templates

- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
{
  CLKComplicationTemplate *template = [self getComplicationTemplateForComplication:complication forEndDate:nil orBodyText:@"doing nothing" withHeaderText:@"placeholder"];
  // This method will be called once per supported complication, and the results will be cached
  handler(template);
}

@end

也许您可以在自己的应用中看到自己的并发症是否存在同样的问题。

我不认为我做错了什么,没有什么可以导致这种奇怪的行为,只是对我来说感觉像个错误。不幸的是,它破坏了我的应用程序,它可以在某些情况下使用非常小的时间轴条目,而且如果用户注意并在测试时保持监视屏幕,我宁愿不使用它们。

感谢您的时间,

干杯

1 个答案:

答案 0 :(得分:0)

关于开始日期之前的条目的错误是准确的,并且该条目被正确丢弃。

原因是getTimelineEntriesForComplication:afterDate:意味着在指定日期之后返回以后的条目。你所做的是在指定日期之前返回一个条目。

  

提供未来参赛作品的开始日期。时间表条目的日期应在此日期之后发生,并尽可能接近日期。

如果没有任何已发布的代码需要检查,我猜您的beforeDate: / afterDate:条件代码是相反的。

有关每分钟多个时间轴条目的其他问题:

关于十秒钟的时间间隔,我可以指出几个问题供您考虑:

  1. 并发症服务器将请求有限数量的条目。

    提供您的输入将始终相隔十秒,并且服务器请求100个条目(在过去或未来方向),相当于1000秒(17分钟以下)。这一般引入了两个问题:

    • 鉴于时间轴的跨度很短,时间线需要每小时更新几次。这不是节能的,如果您经常延长或重新加载它,您可能会耗尽每日并发症预算。

    • 如此短暂的持续时间,时间旅行实际上是无用的,因为旋转数字皇冠可以快速行进超过16分钟(过去或未来),在那里不会(最初)进一步进入显示。

  2. 并发症服务器缓存有限条目数。

    即使您延长时间线,服务器最终也会从扩展时间线中删除(丢弃)条目。假设间隔为10秒,并发症服务器只能保留约2小时的缓存条目。此外,您无法控制哪些条目将被丢弃。

  3. 从时间旅行的角度来看,时间旅行时,每6个时间轴条目中就有5个不会显示(因为时间旅行会按分钟变化)。

    这肯定会给用户带来一些困惑,因为他们无法查看每个条目。

  4. 有关更新限制的说明:

    虽然可以每分钟添加多个条目,但您可能需要重新考虑这是否切实可行。考虑到大多数用户不会观察到大多数频繁的变化,很可能会浪费很多精力来获得很少的收益。

    关于能源效率,您还应该考虑Apple对更新并发症的严格限制。即使在watchOS 3中(鼓励您在后台定期更新复杂功能和停靠快照),您也会遇到每小时4次后台更新限制,每天50次并发症更新。有关详细信息,请参阅watchOS - Show realtime departure data on complication