SurfTideΔとSurfTideWatchに、AppleWatchのコンプリケーションを実装したときのメモです。自分用の備忘録なので、それほど親切には書いていませんが、ネットの情報の不足分を補完する感じです。
タイド情報(潮汐)は常に変化するので、以下のような条件でシンプルに実装しています。
- 一定時間で更新する必要がある
- 毎回タイドを計算するのは冗長なので当日と前後1日分のデータはキャッシュする
- 複数の種類のコンプリケーションを準備する
で、まずはXCODEプロジェクトのWatchExtensionに、コンプリケーションを利用するよう設定します。
というのも、自分の実装では「ComplicationController.swift」関連の設定が無かったり(古いプロジェクト)自分で削除していたりしたので、まずはそれを正しく構成しなければなりませんでした。
まずは「Info.plist」にコンプリケーション関連の設定を追加。
必要なKeyは以下2つです。「CLKComplicationPrincipalClass」は「ComplicationController.swift」のパスを指定します。「CLKComplicationSupportedFamilies」は使うタイプのComplicationsだけ設定します。
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>CLKComplicationSupportedFamilies</key>
<array>
<string>CLKComplicationFamilyModularSmall</string>
<string>CLKComplicationFamilyUtilitarianSmall</string>
<string>CLKComplicationFamilyUtilitarianSmallFlat</string>
<string>CLKComplicationFamilyUtilitarianLarge</string>
<string>CLKComplicationFamilyCircularSmall</string>
<string>CLKComplicationFamilyExtraLarge</string>
<string>CLKComplicationFamilyGraphicCorner</string>
<string>CLKComplicationFamilyGraphicBezel</string>
<string>CLKComplicationFamilyGraphicCircular</string>
</array>
次に「ComplicationController.swift」を用意します。
これはネットの情報は錯綜(海外情報も)してて、古いバージョンやらなんやらで、結局何が必要なのかイマイチでした。
で、自分で実装したのは以下2のメソッドだけです。
ちなみに、最新のXCODEが作ってくれるテンプレートもイマイチ??で使いませんでした。そもそもWatchOS7でしか利用できないようでしたしね。
- getLocalizableSampleTemplate(設定するときのプレ表示用)
- getCurrentTimelineEntry(実際の表示用)
端折ってますがこんな感じです。実際は「getTemplate」の中で、「Info.plist」で設定した分のコンプリケーションテンプレート(CLKComplicationTemplate)を作ってます。
また、キャッシュするデータもこのクラスのメンバ変数として保存します。自分のアプリの場合、場所が変わった場合や、日付が変わった場合をトリガーにしてキャッシュを書き換えています。
class ComplicationController: NSObject, CLKComplicationDataSource {
・
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
setTideData(date: Date())
if let template = getTemplate(complication.family) {
handler(template)
return
}
handler(nil)
}
・
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
let date = Date()
setTideData(date: date)
if let template = getTemplate(complication.family) {
let entry = CLKComplicationTimelineEntry(
date: date,complicationTemplate: template)
handler(entry)
return
}
handler(nil)
}
・
}
次に、各テンプレートが使うアイコンリソースですが、これも必要な分だけ実装します。足りない分は別途ImageSetで追加してもOK。
ちなみに、背景は全て透過で作成します。これを「ComplicationController.swift」の中で各テンプレートに設定します。

最後に、一定時間で更新する処理を「ExtensionDelegate.swift」に記述して完了です。
追加するのは、「applicationDidEnterBackground」メソッドと、handleメッソッドの中の「case let backgroundTask as WKApplicationRefreshBackgroundTask:」にバックグラウンド処理のスケジュール(scheduleBackgroundRefreshTasks)を入れます。
端折ってますがこんな感じです。
func applicationDidEnterBackground() {
// コンプリケーション更新用スケジュール
scheduleBackgroundRefreshTasks()
}
func handle(_ backgroundTasks: Set) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Schedule the next background update.
self.scheduleBackgroundRefreshTasks()
// Be sure to complete the background task once you're done.
// ↓scheduleBackgroundRefreshTasks を有効にする場合は true に変更
backgroundTask.setTaskCompletedWithSnapshot(true)
・
・
func scheduleBackgroundRefreshTasks() {
let watchExtension = WKExtension.shared()
let targetDate = Date().addingTimeInterval(15.0 * 60.0)
watchExtension.scheduleBackgroundRefresh(withPreferredDate: targetDate, userInfo: nil) { (error) in
// Check for errors.
if let error = error {
print("*** An Background refresh error occurred: \(error.localizedDescription) ***")
return
}
// コンプリケーションデータを更新
let server = CLKComplicationServer.sharedInstance()
for complication in server.activeComplications ?? [] {
server.reloadTimeline(for: complication)
}
}
}
これはどの情報をみても15分に1度みたいだったので、そのタイミングがコンプリケーションのミニマム更新時間みたいです。もちろん、他のアクションから強制更新はできますが、1日に更新できる回数は決まっているみたいです。