【iOS】Workout Session入門
Workout・WorkoutSessionとは?
AppleWatchの1つの機能である「Workout」です。アクティビティが日常生活の活動量を常時計測するのに対して、Workoutはランニングやウォーキング、ジムでのトレーニングなど、運動を計測します。AppleWatchでWorkoutを追跡するには、WorkoutSessionを設定、開始、保存する必要があります。この記事では、WorkoutSessionに関して深堀りします。
サンプルコードを参考に進めます。主にWorkoutの状態を管理しているWorkoutManager
に着目して解説していきます。
セットアップ
セットアップ方法に関して以下にまとめていますので参考までにご覧ください。
権限リクエスト
WorkoutSessionを作成する前に、HealthKitを設定し、アプリが使用するヘルスケアデータの読み取りと共有の許可を要求する必要があります。
// Request authorization to access HealthKit.
func requestAuthorization() {
// The quantity type to write to the health store.
let typesToShare: Set = [
HKQuantityType.workoutType()
]
// The quantity types to read from the health store.
let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .heartRate)!,
HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKQuantityType.quantityType(forIdentifier: .distanceCycling)!,
HKObjectType.activitySummaryType()
]
// Request authorization for those quantity types.
healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { (success, error) in
// Handle error.
}
}
requestAuthorization
メソッドを用いて、ヘルスデータを扱う上での権限のリクエストを実行します。
-
toShare
データの書き込みため -
read
データの読み込み
にヘルスデータタイプ(workout、心拍数、歩行距離etc)を指定してリクエストを行います。
Background Modesの設定
WorkoutSessionを用いるアプリはバックグラウンドで実行可能であるため、WatchKit App Extensionにバックグラウンドモード機能を追加する必要があります。
Workoutを開始する
HKWorkoutConfigurationの設定
HKWorkoutConfiguration()
を生成して、運動の種類、屋内or屋外を設定する。
let configuration = HKWorkoutConfiguration()
configuration.activityType = workoutType
configuration.locationType = .outdoor
HKLiveWorkoutBuilderの生成
HKWorkoutConfiguration()
を使用してHKWorkoutSession
を作成します。その後、HKWorkoutSession
からHKLiveWorkoutBuilder
を取得します。
do {
session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
builder = session.associatedWorkoutBuilder()
} catch {
// Handle failure here.
return
}
HKWorkoutSession・HKLiveWorkoutBuilderについて
HKWorkoutSession
・HKLiveWorkoutBuilder
を用いて、Workoutの状態や心拍数のライブデータなどを取得します。
var session: HKWorkoutSession?
var builder: HKLiveWorkoutBuilder?
HKWorkoutSession
・HKLiveWorkoutBuilder
の役割は以下のとおりです。
HKWorkoutSession
(Session):
- ワークアウト全体の期間と状態を管理します。
- ワークアウトの開始、一時停止、再開、終了を制御します。
- イベントやエラーの通知します。
HKLiveWorkoutBuilder
(Builder):
- セッション中に収集されるデータの記録と管理します。
- リアルタイムでのデータサンプルとイベントの収集します。
Delegateの設定
session.delegate = self
builder.delegate = self
を実装し、デリゲートメソッドを実装します。詳細に関しては後述いたします。
HKLiveWorkoutDataSource
の生成
HKLiveWorkoutDataSource
とは、WorkoutSessionからライブデータを提供するDataSourceです。HKLiveWorkoutDataSource
オブジェクトを作成し、builderに割り当てます。
builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: configuration)
WorkoutのSessionとBuilderを開始
SessionとBuilderを開始します。
// Start the workout session and begin data collection.
let startDate = Date()
session?.startActivity(with: startDate)
builder?.beginCollection(withStart: startDate) { (success, error) in
// The workout has started.
}
Workoutの状態の変更
運動前・運動中・休憩中・終了かの状態をHKWorkoutSession
で管理しています。以下のメソッドでWorkoutの状態を操作することができます。
- 一時停止
session?.pause()
- 再開
session?.resume()
- 終了
session?.end()
SessionとBuilderの終了
ワークアウトが終了した後に、Sessionを終了し、BuilderのendCollection(withEnd:completion:)
・finishWorkout(completion:)
メソッドを呼び出します。
endCollection(withEnd:completion:)
- ワークアウトの終了日時を設定し、ビルダーを無効化します。
- 完了ハンドラーは、終了処理が成功したかどうかを示す
success
と、エラーが発生した場合のerror
を受け取ります。
finishWorkout(completion:)
- ワークアウトとその関連データをHealthKitStoreに保存します。
- 完了ハンドラーは、workout(
HKWorkout
) と、エラーが発生した場合のerror
を受け取ります。
以下、SessionとBuilderの終了の実装例です。
session.end()
builder.endCollection(withEnd: Date()) { (success, error) in
guard success else {
// Handle errors.
}
builder.finishWorkout { (workout, error) in
guard workout != nil else {
// Handle errors.
}
DispatchQueue.main.async() {
// Update the user interface.
}
}
}
Workoutに関連したDelegate
HKWorkoutSessionDelegate
とHKLiveWorkoutBuilderDelegate
が存在します。
-
HKWorkoutSessionDelegate
は、Sessionの状態が変化したとき、またはSessionがエラーで失敗したときに更新を受け取ります。 -
HKLiveWorkoutBuilderDelegate
は、Builderに新しいサンプルやイベントを追加したときに更新を受け取ります。
HKWorkoutSessionDelegateについて
Workoutの状態を通知するメソッド(必須)
必須のメソッドであるworkoutSession(_:didChangeTo:from:date:)
です。Workoutの状態(HKWorkoutSessionState
)が変更されたときに処理されるメソッドです。以下、表です。
引数項目 | 説明 |
---|---|
workoutSession | 変化したworkoutSession。 |
toState | セッションの変更後の状態。HKWorkoutSessionState
|
fromState | セッションの変更前の状態。HKWorkoutSessionState
|
date | 状態の変更がいつ発生したかを示すDate。 |
HKWorkoutSessionState
は、WorkoutSessionの状態を表す列挙型です。以下、表です。
状態 | 説明 |
---|---|
notStarted | トレーニングセッションは開始されていません。 |
prepared | セッションは準備ができていますが、まだ開始されていません。 |
running | トレーニングセッションが実行中です。 |
paused | トレーニングセッションが一時停止されました。 |
stopped | セッションが停止しました。 |
ended | トレーニングセッションが終了しました。 |
stopped
、ended
の違いは、
- セッションを再開・再利用できない。
- トレーニングデータを生成しなくなる。
ことは共通しています。異なる点として、stopped
はアプリをバックグラウンドで動作し続けることができますが、ended
はバックグラウンドも停止されます。
WorkoutのSessionの失敗を通知するメソッド(必須)
必須のメソッドであるworkoutSession(_:didFailWithError:)
があります。このメソッドはSessionの生成・状態管理が失敗した場合に通知されるメソッドです。以下、表です。
項目 | 説明 |
---|---|
workoutSession | 失敗したHKWorkoutSession
|
error | 失敗をに関するError オブジェクト |
HKLiveWorkoutBuilderDelegateについて
新しいヘルスケアデータが追加されたことを通知するメソッド(必須)
必須のメソッドであるworkoutBuilder(_:didCollectDataOf:)
があります。このメソッドは新しいデータがビルダに追加されたことを通知するメソッドです。
引数名 | 型 | 説明 |
---|---|---|
workoutBuilder | HKLiveWorkoutBuilder |
イベントを収集したHKLiveWorkoutBuilder 。ワークアウトデータの詳細を取得するために使用されます。 |
collectedTypes | Set<HKSampleType> |
収集されたサンプルデータ型の集合。HKSampleType の集合であり、特定のサンプルデータ型を示します。 |
使用例を以下に示します。
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
for type in collectedTypes {
guard let quantityType = type as? HKQuantityType else {
return // Nothing to do.
}
let statistics = workoutBuilder.statistics(for: quantityType)
// Update the published values.
updateForStatistics(statistics)
}
}
処理を追っていきます。
for type in collectedTypes {
// 省略
}
collectedTypes内に存在する通知されたサンプルデータの集合を全てfor文
で処理する。
let statistics = workoutBuilder.statistics(for: quantityType)
各データタイプを引数に設定して、workoutBuilderに存在するヘルスデータを取得する。
// Update the published values.
updateForStatistics(statistics)
取得したデータを用いて、UIに関連するプロパティを更新する。
新しいイベントが追加されたことを通知するメソッド(必須)
必須のメソッドであるworkoutBuilderDidCollectEvent(_:)
があります。新しいイベントがビルダに追加されたことを通知するメソッドです。
let lastEvent = workoutBuilder.workoutEvents.last
上記の実装でビルダからイベントを取得できます。イベントはHKWorkoutEvent
で表され、イベントを取得できます。以下、プロパティリストです。
変数名 | 型 | 説明 |
---|---|---|
dateInterval |
DateInterval |
イベントの時間と期間 |
type |
HKWorkoutEventType |
ワークアウトイベントの種類 |
metadata |
[String : Any]? |
ワークアウトイベントに関連するメタデータ |
-
dateInterval
はHKWorkoutEventType.lap
およびHKWorkoutEventType.segment
のみ日時間隔を含むDateInterval
を取得できる。それ以外のEventに関してはイベントが発生した時点の示すdateInterval
(日時間隔が0)の値を取得できます。 -
type
はHKWorkoutEventType
です。以下、表です。
イベント名 | 説明 |
---|---|
pause |
ワークアウトが一時停止されたことを示す定数 |
resume |
ワークアウトが再開されたことを示す定数 |
motionPaused |
システムがセッションを自動的に一時停止したことを示す定数 |
motionResumed |
システムがセッションを自動的に再開したことを示す定数 |
pauseOrResumeRequest |
ユーザーが一時停止または再開を要求したことを示す定数 |
lap |
ラップを示す定数 |
segment |
ワークアウト中の注目すべき期間を示す定数 |
marker |
ワークアウト中の注目すべきポイントを示す定数 |
例として、HKWorkoutEventType.lap
イベントが発生したら、ラップ数のUIを更新するなどが挙げられます。
その他
- iOSとWatchOSを同期させてWorkoutを管理
- Workout中に生じたクラッシュからのリカバリー方法
上記に関して公式に記載されていますが、今回の記事では省きます。以下の公式に記載ありますので、参考までにご覧ください。
WorkoutSessionに関連した主な実装は以上になります。
最後に
間違い・気になる部分がありましたら、コメントいただけると大変うれしいです。
良かったと思ったら記事へのいいね、Xのフォローをよろしくお願いいたします。
個人でアプリを作成しています。良かったら覗いてみてください。
Discussion