2

有没有其他人注意到并发症条目没有正确更新的问题。我刚刚为我的应用程序添加了一些初始支持,但注意到它们没有显示我期望它们显示的内容。例如,为了方便快速测试这个问题,我会创建一个时间线

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

然而,我所看到的只是并发症条目 A 停留在 B 和 C 应该显示的时间之后。

我的普通应用程序本身并没有设置为像这样创建定期间隔的并发症,它具有可以由用户设置的计时器的许多方面,但其中一个方面只是允许用户一次启动多个计时器,这一切将在他们选择的用户定义的持续时间后完成。与 iOS 时钟应用程序的计时器不同,您可以以秒为单位指定计时器持续时间,因此 2 个计时器完全有可能在几秒钟内完成,但总体而言,它们更有可能相隔几分钟。此外,不应该添加太多的复杂条目,尽管我的应用程序的其他更复杂的方面可以轻松添加 10 甚至 100 个复杂条目,具体取决于用户在其中设置的任务的复杂程度。不过就目前而言,

我将 Xcode 更新到了最新版本(7.3.2),没有任何改进,并将构建发送到我的实际手机和手表,同样没有改进。直到它确实起作用了。在进一步的调试中,我发现我可以通过简单地降低我的手表(关闭屏幕)然后再次唤醒它来使时间线自动运行,而这一切都是在它正在执行我的时间线的过程中进行的。完成此操作后,时间线将从那时起正常工作。

我创建了一个测试应用程序来演示该问题,它确实完全重现了该问题,因此我将在错误报告中将其发送给苹果。只是想看看有没有其他人注意到这个问题。

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

-[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 的是我的时间线的 endDate,而不是startDate。我不确定这是否与我看到的行为有关,因为我在隐藏/显示屏幕后看到相同的错误。

我在我的在线测试应用程序中放了一段关于这种行为的视频。此测试应用程序的详细信息如下

应该在相关模拟器中运行的完整代码在这里可用,相关的复杂模块也在这里列出以供参考。

在我的扩展委托中,我从 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 中有以下方法来处理事情的复杂性

并发症控制器.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

也许你可以看看你自己的应用程序是否有同样的问题。

我不认为我做错了什么,没有什么会导致这种奇怪的行为,对我来说就像一个错误。不幸的是,它破坏了我的应用程序,该应用程序在某些情况下可以使用非常小的时间线条目,如果用户注意并在测试时保持手表屏幕打开,我宁愿不让它们不起作用。

谢谢你的时间,

干杯

4

1 回答 1

1

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

原因是为了在指定日期之后getTimelineEntriesForComplication:afterDate:返回未来的条目。您所做的是在指定日期之前返回一个条目。

提供未来条目的开始日期。您的时间线条目的日期应在此日期之后,并尽可能接近该日期。

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

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

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

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

    提供您的条目将始终相隔 10 秒,并且服务器请求 100 个条目(过去或未来方向),这相当于 1000 秒(不到 17 分钟)。这通常会引入两个问题:

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

    • 在如此短的持续时间下,时间旅行实际上是没有用的,因为旋转数字表冠可以快速移动超过 16 分钟到(过去或)未来,(最初)不会有任何进一步的条目显示。

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

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

  3. 从时间旅行的角度来看,每 6 个时间线条目中有 5 个永远不会在时间旅行时显示(因为时间旅行以分钟为单位变化)。

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

关于更新限制的说明:

虽然您可以每分钟添加多个条目,但您可能需要重新考虑这是否可行。考虑到大多数用户不会观察到大多数频繁的变化,这很可能是白费力气而收效甚微。

关于能源效率,您还应该考虑 Apple 对更新复杂功能施加的硬性限制。即使在 watchOS 3 中(鼓励您在后台定期更新复杂功能和停靠快照),您也会遇到每小时 4 次后台更新和每天 50 次复杂功能更新的限制。请参阅watchOS - 显示有关并发症的实时出发数据以了解详情。

于 2016-06-16T02:57:17.993 回答