モチベーション
運動関係のアプリを作るのが好きだ。過去に作ったiOSアプリを使っていたが、いちいちアプリを開いて記録を見るのが手間なのでウィジェットで実現したかった。
ウィジェットは単体では存在することができず、iOSアプリなどのアプリケーションターゲットと抱合せで作る必要がある。これはマルチモジュールが効くんじゃないかなと思ってマルチモジュールでウィジェットの開発を試みた。
つくったもの
一週間ごとに散歩の歩行距離の目標値を立てて、その目標に対して今どの位置にいるのかを表示するウィジェットを作った。目標を入力するところはiOSアプリ。
ウィジェット
iOSアプリで登録した目標とする歩行距離のうち、実際に今どのくらい歩いているのかをHealthKit経由で運動データを取得して表示している。
iOSアプリ
ウィジェットで表示する目標値を入力する。HealthKitの権限リクエストもウィジェットではできないのでここでやっている。
ディレクトリ構成
$tree -L 2 command tree -a -I .git . ├── .gitignore ├── App │ ├── Package.swift │ ├── leaper │ ├── leaper.xcodeproj │ ├── leaperWidget │ └── leaperWidgetExtension.entitlements ├── Package.swift ├── Sources │ ├── HealthKitService │ └── RootScreen └── Tests └── leaperTests
技術的なポイント
- WidgetKitのライフサイクルを知った上で作る必要がある
TimelineProvider*1 に時系列な処理を書く- コード上で更新タイミングを指定できるが、システム側の状況によって実際の更新タイミングが変わるそうで、あくまで更新タイミングの希望を書く形
- 普段自分が開発しているiOSアプリと同じようにSwiftUIを使うことができる
- 前述のTimelineProvider以外
ObservableObjectなどは意図した通りに機能しなかった
- iOSアプリで目標値を入力して、App Groups*2の仕組みを使ってiOSアプリとウィジェットで同じ値を参照するようにした
- ウィジェットだけでは入力を受け付けることができない
- 運動データはHealthKit経由で取得している
HKQuantityType(.distanceWalkingRunning)- これはApple Watchだけでなく、iPhoneだけでも取れる値なのが良い
- HealthKit関連の処理をHealthKitServiceモジュール内に書いたので、iOSアプリとウィジェットの両方でモジュールを使うことができる