Apple Watch OS 2 ClockKit complication(時計画面の表示)について


##概要
・Watch OS 2にて、対応されるClockKitの
Complication(時計画面の編集)について、調べたことをざっくりとですが書こうと思います。

・8/13時点最新のXcode7 bete5/iOS9 bete5/Watch OS 2 bete5で確認しました。

・まだそんなに情報量多くないので、間違ってることもあるかもしれません。その際はお手数ですがご指摘をお願いします。

・主な参考資料
https://developer.apple.com/videos/wwdc/2015/?id=209
https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/ClockKit_framework/index.html#//apple_ref/doc/uid/TP40016082


##Complicationとは
・Watchの時計画面の一部を編集できる機能。
　具体的には以下の黄色枠の部分にアプリ独自の画像やテキストを表示できる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37373/5ad0621d-b36e-20ea-bf69-ccde3a4a69b3.png "image.png")

・画像は以下のような２色まで利用可能。foreground(以下の場合、飛行機部分)色は白 or 黒を指定(実際は白/黒100%にはならない様子)。
![ airplane.png](https://qiita-image-store.s3.amazonaws.com/0/37373/48beed5b-941d-a62c-0b9b-ba43bb4e1847.png " airplane.png")

*上記のような画像生成コード例
> UIImage bgImage = [UIImage imageNamed:@"Background"];
> UIImage fgImage = [UIImage imageNamed:@"Foreground"];
> CLKImageProvider* imageProvider = [CLKImageProvider imageProviderWithBackgroundImage:bgImage backgroundColor:[UIColor blueColor] foregroundImage:fgImage foregroundColor:CLKImageProviderForegroundColorWhite];

* 画像サイズ
	* 左上の丸い箇所(その他についても随時書いてきます、多分、、)

	| Apple Watch size    | Width       | Height       |
|:--------------------|:------------|:-------------|
| 38mm         |32 pixels|32 pixels|
| 42mm         |36 pixels|36 pixels|

	※https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/CLKComplicationTemplateCircularSmallSimpleImage_class/index.html#//apple_ref/occ/instp/CLKComplicationTemplateCircularSmallSimpleImage/imageProvider



##実装
・プロジェクト作成で、[iOS App with WatchKit App]選択後、[Include Complication]をチェック。
	![project_include_complication.png](https://qiita-image-store.s3.amazonaws.com/0/37373/13ff713e-a1b9-a0b1-d785-71f0b47d7cf5.png "project_include_complication.png")

・WatchExtentionのプロジェクトの設定で、どの部分（上記の黄色で囲んだところ）を対応するかをチェックで選択できる。
	![project_include_complication2.png](https://qiita-image-store.s3.amazonaws.com/0/37373/12e6abe4-881a-1782-1f35-392a5e2dcd9a.png "project_include_complication2.png")
	

・上記にて、作成されるComplicationController.h/ComplicationController.mが
　Complicationの処理を書くソースファイル。


###CLKComplicationDataSourceの各メソッドについて
・CLKComplicationDataSourceの各メソッドは、WatchOS（ClockKit）が時計画面に表示データをセットするタイミングでコールする。

・実行されるプロセスはWatchKit extensionのメインスレッド。

・CLKComplicationDataSourceの各メソッド内でExtensionDelegateオブジェクトを取得できる（[[WKExtension sharedExtension] delegate]にて取得できる）。

・ClockKitのCLKComplicationDataSourceの各メソッド実行時に、対象WatchアプリのWatchKit extensionプロセスがバックグラウンド起動するが、 **applicationDidFinishLaunchingなどのWKExtensionDelegateメソッドはコールされなかった** 。


・以下、CLKComplicationDataSource各メソッドについてです。Sample Codeはとりあえずなにか表示する、といったレベルのものです。


####- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication 
・現在時刻にたいして、過去/未来についてデータセットするかを設定(過去/未来/両方)

```
// Sample Code
- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler {
    handler(CLKComplicationTimeTravelDirectionForward|CLKComplicationTimeTravelDirectionBackward);//過去/未来両方対応
}
```
<br/>

####- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable NSDate *date))handler
・サポートする開始日付

```
// Sample Code
- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable NSDate *date))handler {
    handler([NSDate dateWithTimeIntervalSinceNow:-1*24*60*60]);//１日前からサポート
}
```
<br/>

####- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable NSDate *date))handler 

・サポートする終了日付

```
// Sample Code
- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable NSDate *date))handler {
    handler([NSDate dateWithTimeIntervalSinceNow:1*24*60*60]);//１日後までサポート
}
```
<br/>

####- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handle
・ロック時に表示する/しないデータを指定。
・データというか、complication.famiry(時計画面のどの部分に表示されているか)によって、ロック中に表示するかどうかをセットできる。

```
// Sample Code
- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handler {
    handler(CLKComplicationPrivacyBehaviorShowOnLockScreen);//ロック画面中も全て表示
}
```
<br/>

####- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable CLKComplicationTimelineEntry *))handler

・現在時刻(ClockKitが実行されたタイミング)のタイムラインに表示するデータをセット

```
// Sample Code
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable CLKComplicationTimelineEntry *))handler {
    // Call the handler with the current timeline entry
	switch (complication.family) {
		case CLKComplicationFamilyCircularSmall: {
			CLKComplicationTemplateCircularSmallSimpleImage* simpleImage = [CLKComplicationTemplateCircularSmallSimpleImage new];
			simpleImage.imageProvider = [CLKImageProvider imageProviderWithBackgroundImage:[UIImage imageNamed:@"icon_xxx"] backgroundColor:[UIColor redColor]];
			CLKComplicationTimelineEntry* entry = [CLKComplicationTimelineEntry entryWithDate:[NSDate date] complicationTemplate:simpleImage];
			handler(entry);
		}
			break;
			
		default:
			handler(nil);
			break;
	}
}
```
<br/>


####- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(__nullable NSArray<CLKComplicationTimelineEntry *> *entries))handler


・過去のタイムラインに表示するデータをセット
・引数で渡されるdate以前の日時のCLKComplicationTimelineEntryデータをセットできる。
・セットできるCLKComplicationTimelineEntryデータの日時の範囲は、getTimelineStartDateForComplication:withHandler:のhandlerで指定した日時 =< セットできるデータ =< 引数で渡されるdate。

```
// Sample Code
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(__nullable NSArray<CLKComplicationTimelineEntry *> *entries))handler {
    // Call the handler with the timeline entries prior to the given date
	switch (complication.family) {
		case CLKComplicationFamilyCircularSmall: {
			CLKComplicationTemplateCircularSmallSimpleImage* simpleImage = [CLKComplicationTemplateCircularSmallSimpleImage new];
			simpleImage.imageProvider = [CLKImageProvider imageProviderWithBackgroundImage:[UIImage imageNamed:@"icon_xxx"] backgroundColor:[UIColor redColor]];
			
			CLKComplicationTimelineEntry* entry = [CLKComplicationTimelineEntry entryWithDate:date complicationTemplate:simpleImage];
			handler(@[entry]);//ここはLimitまでの要素数でNSArrayを渡せる
		}
			break;
			
		default:
			handler(nil);
			break;
	}
}
```
<br/>


####- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(__nullable NSArray<CLKComplicationTimelineEntry *> *entries))handler

・未来のタイムラインに表示するデータをセット
・引数で渡されるdate以降の日時のCLKComplicationTimelineEntryデータをセットできる。
・セットできるCLKComplicationTimelineEntryデータの日時の範囲は、引数で渡されるdate =< セットできるデータ  =< getTimelineEndDateForComplication:withHandler:のhandlerで指定した日時。

```
// Sample Code
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(__nullable NSArray<CLKComplicationTimelineEntry *> *entries))handler {
    // Call the handler with the timeline entries after to the given date
	switch (complication.family) {
		case CLKComplicationFamilyCircularSmall: {
			CLKComplicationTemplateCircularSmallSimpleImage* simpleImage = [CLKComplicationTemplateCircularSmallSimpleImage new];
			simpleImage.imageProvider = [CLKImageProvider imageProviderWithBackgroundImage:[UIImage imageNamed:@"icon_xxx"] backgroundColor:[UIColor redColor]];
			
			CLKComplicationTimelineEntry* entry = [CLKComplicationTimelineEntry entryWithDate:date complicationTemplate:simpleImage];
			handler(@[entry]);//ここはLimitまでの要素数でNSArrayを渡せる
		}
			break;
			
		default:
			handler(nil);
			break;
	}
}
```
<br/>

####- (void)getNextRequestedUpdateDateWithHandler:(void(^)(__nullable NSDate *updateDate))handler 

・WatchOS(ClockKit)に、次回complication(時計画面)を更新したい日時をセット。
・１時間以上後の日時を指定しなければならない。
　→１日にcomplication更新できる回数には制限があって（その回数は取得できるわけではなさそうですが）、それを超えると更新されない。

```
// Sample Code
- (void)getNextRequestedUpdateDateWithHandler:(void(^)(__nullable NSDate *updateDate))handler {
    // Call the handler with the date when you would next like to be given the opportunity to update your complication content
    handler([NSDate dateWithTimeIntervalSinceNow:60 * 60]);//1時間後
}
```


・handlerで指定した日時くらい（実際、この日時から5~15分くらい遅れて動作している気がしました）に[requestedUpdateDidBeginメソッド](#requestedUpdateDidBegin) or [requestedUpdateBudgetExhaustedメソッド](#requestedUpdateBudgetExhausted)がコールされます。 **このrequestedUpdateDidBegin or requestedUpdateBudgetExhaustedメソッドを実装しないと指定した日時でのcomplication更新が行われない。**


<br/>


#### <a name ="requestedUpdateDidBegin">- (void)requestedUpdateDidBegin

・reloadTimelineForComplication:(現在のタイムラインデータを破棄して更新)または extendTimelineForComplication:(現在のタイムラインデータに拡張) で、complication更新する。


```
// Sample Code
- (void)requestedUpdateDidBegin {
    CLKComplicationServer* server = [CLKComplicationServer sharedInstance];
	for (CLKComplication* complication in server.activeComplications ) {
		//表示されてるComplecationを更新
		[server reloadTimelineForComplication:complication];
	}
}
```


<br/>


#### <a name ="requestedUpdateBudgetExhausted">- (void) requestedUpdateBudgetExhausted

・reloadTimelineForComplication:(現在のタイムラインデータを破棄して更新)または extendTimelineForComplication:(現在のタイムラインデータに拡張) で、complication更新する。
・このメソッドがコールされた時は、制限回数に達してしまったため、更新の最後チャンス。制限回数が補充されるまで（多分、１日ごとに補充される）次の更新が行われない。


```
// Sample Code
- (void) requestedUpdateBudgetExhausted {
    CLKComplicationServer* server = [CLKComplicationServer sharedInstance];
	for (CLKComplication* complication in server.activeComplications ) {
		//表示されてるComplecationを更新
		[server reloadTimelineForComplication:complication];
	}
}
```



<br/>


####- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable CLKComplicationTemplate *complicationTemplate))handler

・時計のカスタマイズで選んでるときに出てくる見本をセット。
・これは一回読み込みされたらWatchにキャッシュされる。キャッシュクリアするにはWatchアプリ再インストール。

```
// Sample Code
- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(__nullable CLKComplicationTemplate *complicationTemplate))handler {
    // This method will be called once per supported complication, and the results will be cached

	switch (complication.family) {
		case CLKComplicationFamilyCircularSmall: {
			UIImage* ima = [UIImage imageNamed:@"icon_xxx"];
			CLKComplicationTemplateCircularSmallSimpleImage* simpleImage = [CLKComplicationTemplateCircularSmallSimpleImage new];
			simpleImage.imageProvider = [CLKImageProvider imageProviderWithBackgroundImage:ima backgroundColor:[UIColor redColor]];
			handler(simpleImage);
		}
			break;
			
		default:
			handler(nil);
			break;
	}
}
```
<br/>

###Complicationの更新
・上記にも記載した通りで、getSupportedTimeTravelDirectionsForComplication、getSupportedTimeTravelDirectionsForComplication、getTimelineEndDateForComplication で
　どの日時までComplicationでデータセットするかを決めて、getCurrentTimelineEntryForComplication、getTimelineEntriesForComplication、getTimelineEntriesForComplicationで
　データセット。
　数時間分のデータをComplication側にセットしておくイメージ。

・getNextRequestedUpdateDateWithHandlerで次に更新してもらいたい日時をセットする(数分以内はNG)。そしてその日時くらいになったらrequestedUpdateDidBegin or requestedUpdateBudgetExhaustedメソッドがコールされるのでそこでComplication更新。
・Watchアプリ側からも以下のコードを実行するとComplication更新できる。でもやりすぎると制限回数を超えてしまって更新されなくなる。

```
// Sample Code
#import <ClockKit/ClockKit.h>
　・
　・
　・
-(void)updateComplication {
	CLKComplicationServer* server = [CLKComplicationServer sharedInstance];
	for (CLKComplication* complication in server.activeComplications ) {
		//ActiveなComplecationを更新
		[server reloadTimelineForComplication:complication];
	}
}
	
```
<br/>



###Complicationで使用するデータ
・Watch内に保存されたデータを利用する、とReferenceには書かれいる。
　→ Complication内で時間のかかる処理（通信など）ができないため
・WCSessionクラスのupdateApplicationContext:error:などでiPhone側からあらかじめデータをWatch側に送信しておくのがよさそう。


##Debug して思ったこと(8/13時点、Xcode7 beta5にてデバッグ)

・実機でたまにWatchアプリが起動しなくなる（ずっとくるくる回ってロード中状態）。その場合はAppleWatchを再起動で回復する。
・それでもだめならWatchApp再インストール。
・それでもだめならiPhoneアプリも再インストール。
・それでもだめならiPhone/AppleWatch再起動。

・complicationは、現在は実機よりもsimulatorの方がDebugしやすい印象（私だけかもしれないが、、、）。AppleWatch実機だと上記のようにWatchアプリが起動しなくなるケースが何度かあった。
→beta5からはAppleWatch実機debugもしやすくなった。