2016-06-15 11 views
1

다른 사람이 합병증 항목이 제대로 업데이트되지 않는 문제를 발견했습니다. 방금 내 앱에 초기 지원을 추가했지만 표시 할 것으로 기대되는 것을 표시하지 않는 것으로 나타났습니다. 예를 위해서, 빨리 내가 ​​타임 라인시계 실행 도중 시계면이 숨겨져 있지 않으면 Apple Watch ClockKit 합병증이 타임 라인 항목을 업데이트하지 않습니다.

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에 내 타임 라인의 종료일이라고 말한 내용이었습니다 startDate입니다. 이것은 화면을 숨기거나 표시 한 후에도 작동 할 때 같은 오류가 나타나는 것을보고있는 것과 관련이 있는지 확실하지 않습니다.

나는이 동작 비디오를 내 테스트 응용 프로그램 here에 넣었습니다. 이 테스트 응용 프로그램의 세부 사항은 다음과 같습니다

해당 시뮬레이터에서 실행되어야하는 전체 코드는 here입니다. 해당되는 복잡한 모듈도 참조 용으로 여기에 나열되어 있습니다. 내 확장 위임에

, 난 iOS 앱에서 사용자 정보를 받아 내 합병증의 타임 라인

ExtensionDelegate의 새로 고침을 예약합니다.일

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 

의 합병증 측면을 처리하기 위해 다음과 같은 방법이 같은 문제가 발생하면 다음

- (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]; 
    } 
} 

m 내 ComplicationController에 아마 당신은 볼 수 있습니다 자신의 응용 프로그램에서 자신의 합병증.

나는 이상한 행동을 일으키는 어떤 것도 잘못하고 있다고 생각하지 않습니다. 단지 버그라고 느낍니다. 불행히도 어떤 경우에는 아주 작은 타임 라인 항목으로 작업 할 수있는 내 앱을 훼손하는 앱이며, 사용자가주의를 기울이고 테스트 화면에서 시계 화면을 계속 유지하면 오히려 제대로 작동하지 않게됩니다. 시간에 대한

감사합니다,

건배

답변

0

시작 날짜 전에 항목에 대한 오류는 정확하고 항목이 제대로 삭제됩니다.

그 이유는 getTimelineEntriesForComplication:afterDate:은 이후에 미래의 항목 을 반환 할 예정입니다. 네가 한 것은 지정된 날짜 이전에 엔트리를 반환하는 것이다.

향후 항목을 제공하기위한 시작일. 타임 라인 항목의 날짜는이 날짜 이후에 발생해야하며 가능한 한 날짜에 가까운 날짜 여야합니다. 검사 할 게시 된 코드없이

, 나는 당신의 beforeDate:/afterDate: 조건 코드가 반전되는 것을 추측에는 요.

분당 여러 타임 라인 항목에 대한

다른 문제 : 열 개 두 번째 시간 간격에 관한

, 나는 몇 가지 문제를 지적 할 수 고려해야 할 :

  1. 합병증 서버가 요청됩니다 제한된 수의 항목.

    항목을 제공하는 데는 항상 10 초 간격이 필요하며 서버는 과거 또는 미래 방향으로 100 항목을 요청했으며 그 값은 1000 초 (17 분 미만)입니다. 타임 라인의 짧은 기간을 감안할 때

    • 는, 타임 라인이 여러 번 한 시간을 업데이트 할 필요가있을 것이다 : 이것은 일반적으로 두 가지 문제를 소개합니다. 이것은 에너지 효율적이지 않으며, 너무 자주 확장하거나 재 장전하는 경우 매일 복잡한 합병증을 피할 수 있습니다.

    • 디지털 왕관을 돌리면 (과거 또는 미래에) 16 분 이상 빠르게 이동할 수 있기 때문에 시간 이동이 효과적이지 않습니다. 처음에는 표시 할 항목이 더 이상 없습니다. .

  2. 합병증 서버 엔트리 한정 번호를 캐시.

    타임 라인을 연장하더라도 서버는 확장 된 타임 라인에서 항목을 제거 (폐기)합니다. 다시 10 초 간격으로 가정하면, 복잡한 서버는 약 2 시간 분량의 캐시 된 항목 만 유지할 수 있습니다. 또한 어떤 항목이 삭제 될지에 대한 제어권이 없습니다.

  3. 시간 여행의 관점에서 볼 때 시간 이동이 분 단위로 변경되기 때문에 시간 이동 중에는 6 개의 시간대 항목 중 5 개가 표시되지 않습니다.

    모든 항목을 볼 수 없으므로 사용자에게 혼란을 줄 것입니다. 업데이트 한계에 대한

주의 사항 : 당신이 분에 여러 항목을 추가 할 수 있지만

, 그 실천 여부를 재고 할 수 있습니다. 대부분의 사용자가 빈번한 변경 사항의 대부분을 준수하지 않는다는 것을 고려하면 거의 이익을 얻으려면 많은 노력이 낭비 될 것입니다.

에너지 효율과 관련하여 Apple에서 합병증을 업데이트하기 위해 부과하는 엄격한 제한도 고려해야합니다. WatchOS 3 (백그라운드에서 복잡성을 유지하고 스냅 샷을 정기적으로 업데이트하는 것이 권장되는 곳)에서도 시간당 4 회의 백그라운드 업데이트 제한과 하루에 50 건의 합병증 업데이트에 대해 실행됩니다. 자세한 내용은 watchOS - Show realtime departure data on complication을 참조하십시오.

+0

이와 같은 자세한 답장을 보내 주셔서 감사합니다. 내 게시물을 편집하여 코드를 표시 한 다음 다시 포인트에 응답합니다. 건배! – jimbobuk

+0

다시 한 번 고맙습니다. 자세한 답장을 보내 주셔서 감사합니다. 합병증은 내 자신의 검색에서 여기에 대해 많은 관심을 보이지 않는 것 같습니다. 필자는 이제 원래의 게시물을 전체 코드 스 니펫과 함께 일반 앱의 배경과 시뮬레이터에서 실행할 수 있어야하는 전체 프로젝트에 대한 토론으로 업데이트했습니다. 제 합병증 afterDate 메서드가 건전하다는 것을 알았 으면 좋겠군요. 출력 로그에서 내가 전달한 날짜가 정확한지 확인할 수 있습니다. startDate 시스템 오류가 실제로 내 종료 날짜임을 알 수 있습니다 !! – jimbobuk

+0

초기 정보 업로드 후 새로운 합병증 정보가 시스템에 전송되지 않으므로 사용자가 잠금/쇼 시계 앞면 기술을 수행하면 OS에서 버그를 암시하는 합병증이 적절하게 작용할 수 있습니다. 나는 또한 내 주 앱에 대해 토론하는 원본 게시물 상단에 약간의 메모를 추가했습니다. 이 10 초 간격의 복잡한 항목을 만들지는 않습니다. 테스트 애플리케이션을 만들어서 문제를 재현하고 사용자 작업에 따라 내 비디오에 표시된 것처럼 작동하지 않는 것을 빠르고 쉽게 보여줍니다. – jimbobuk