Apple Watch ClockKit Complications не обновляют свои записи временной шкалы, если циферблат не скрыт во время выполнения

Кто-нибудь еще заметил проблему с записями усложнения, не обновляющимися должным образом. Я только что добавил некоторую начальную поддержку в свое приложение, но заметил, что они не отображают то, что я ожидал, что они отобразят. Ради примера, и для простоты быстрого тестирования этой проблемы я бы создал временную шкалу

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

Тем не менее, все, что я видел, было осложнением, вход A после того, как B и C должны были отображаться.

Мое обычное приложение само по себе не предназначено для создания регулярно разнесенных усложнений, подобных этому, оно имеет много аспектов таймеров, которые оно выставляет, которые могут быть установлены пользователем, но один такой аспект просто позволяет пользователю запускать несколько таймеров одновременно, которые все закончится после того, как пользователь определит продолжительность, которую они выбирают. В отличие от таймера приложения iOS, вы можете указать длительность таймера в секундах, и поэтому вполне возможно, что 2 таймера завершат работу за секунды, в целом, хотя более вероятно, что они будут на расстоянии нескольких минут. Также не должно быть добавлено слишком много записей о сложностях, хотя другие более сложные аспекты моего приложения могут легко добавить 10 или даже ~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, было endDate моей временной шкалы, а не startDate. Я не уверен, связано ли это с поведением, которое я вижу, поскольку я вижу ту же ошибку, когда она работает после того, как я скрываю / показываю экран.

Я разместил видео этого поведения в моем тестовом приложении онлайн здесь. Детали этого тестового приложения следующие

Полный код, который должен быть запущен в соответствующих симуляторах, доступен здесь, соответствующие модули сложности также перечислены здесь для справки.

В моем делегате расширения я получаю userInfo из приложения для iOS и планирую перезагрузку моей временной шкалы осложнений.

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 ответ

Ошибка в записи до даты начала является точной, и запись корректно отбрасывается.

Причина в том что getTimelineEntriesForComplication:afterDate: предназначен для возврата будущих записей после указанной даты. Что вы сделали, так это вернули запись до указанной даты.

Дата начала для предоставления будущих записей. Даты ваших записей на временной шкале должны быть указаны после этой даты и быть как можно ближе к дате.

Без какого-либо размещенного кода для проверки, я думаю, что ваш beforeDate:/afterDate: условный код меняется на противоположный.

Другие проблемы, связанные с несколькими записями временной шкалы в минуту:

Что касается десятисекундного интервала времени, я могу указать вам на несколько вопросов:

  1. Сервер усложнения запросит ограниченное количество записей.

    Предоставление ваших записей всегда будет с интервалом в десять секунд, и что сервер запросил 100 записей (в прошлом или будущем направлении), что составляет 1000 секунд (менее 17 минут). Это вводит две проблемы в целом:

    • Учитывая короткий промежуток времени, график необходимо будет обновлять несколько раз в час. Это не является энергоэффективным, и вы можете исчерпать свой ежедневный бюджет на усложнение, если вы продлеваете или перезагружаете его слишком часто.

    • При такой короткой продолжительности путешествие во времени фактически бесполезно, поскольку вращение цифровой короны может быстро продвинуться более чем на 16 минут в (прошлое или) будущее, где (первоначально) не будет никаких других записей для отображения.

  2. Сервер усложнения кэширует ограниченное количество записей.

    Даже если вы продлите свою временную шкалу, сервер в конечном итоге удалит (откажется) записи с расширенной временной шкалы. Опять же, при условии 10-секундного интервала, сервер усложнения сможет хранить в кэше записи только около 2 часов. Кроме того, вы не можете контролировать, какие записи будут отбрасываться.

  3. С точки зрения путешествий во времени, 5 из каждых 6 записей временной шкалы никогда не будут отображаться во время путешествий во времени (так как путешествие во времени меняется на минуты).

    Это, безусловно, может ввести пользователя в заблуждение, так как он не сможет просматривать каждую запись.

Примечание об ограничениях обновлений:

Хотя вы можете добавить несколько записей в минуту, вы можете переосмыслить, насколько это практично. Учитывая, что большинство пользователей не наблюдают большинство частых изменений, скорее всего, это много потраченных впустую усилий за небольшую выгоду.

Что касается энергоэффективности, вы также должны учитывать жесткие ограничения, которые Apple устанавливает для обновления сложности. Даже в watchOS 3 (где рекомендуется регулярно обновлять фоновый снимок сложности и дока), вы столкнетесь с лимитом 4 фоновых обновления в час и 50 обновлений сложности в день. Смотрите watchOS - Показать в реальном времени данные о сложностях для уточнения.

Другие вопросы по тегам