ハイライト
iOS 17、watchOS 10、iPadOS 17 では HealthKit に 3 つの中核機能が追加されました。Apple Watch と iPhone 間のリアルタイム運動ミラーリング、サイクリング専用データ型(ケイデンス、パワー、速度、FTP)、および iPad 向け HealthKit です。1 セットの API で 3 デバイスをカバーする運動 App を構築できます。
主要内容
課題: 運動 App はかつて Watch 上だけで完結していた
Apple Watch 向け運動 App を開発したことがあるなら、こんな場面を知っているはずです。ユーザーが屋外でサイクリングし、Watch を手首に付けたままデータを見ようと腕を上げる — 走行中には安全でも快適でもありません。ハンドルに固定した iPhone や、室内トレーナー前の iPad など、より大きな画面は HealthKit のリアルタイムデータが Watch でしか取れないため、使われずに放置されます。
さらに、サイクリングで生まれるデータは心拍やカロリーよりはるかに多様です。ケイデンス(1 分あたりのペダル回転数)、パワー(ワット出力)、速度 — watchOS 9 以前、HealthKit はこれらを直接サポートしていませんでした。開発者は指標を諦めるか、Bluetooth センサーを自前で統合し、データを手動解析して HealthKit に戻す方法を自分で考える必要がありました。
Bluetooth センサーの互換性も落とし穴です。ブランドごとにデータ形式とペアリング手順が異なります。ユーザーは別途購入・ペアリング・キャリブレーションが必要です。開発者にとっては、フィットネスと無関係な Bluetooth 通信コードの保守を意味します。
Apple のアプローチ: 3 デバイス連携 + サイクリングのネイティブサポート
WWDC 2023 で Apple はこれらの問題に 3 つの重要機能で応えました。
- ミラーリング運動セッション: Watch 上のリアルタイム運動データを iPhone にミラーし、状態を自動同期。iPhone 側でより豊富なデータパネルを表示できます。
- サイクリング専用データ型: HealthKit がケイデンス、パワー、速度、FTP(Functional Threshold Power)を直接サポート。Watch が Bluetooth センサーから自動収集して HealthKit に書き込みます。
- iPad 向け HealthKit: HealthKit と Health App が iPad に登場。iCloud で健康データを同期し、iPad を大画面の運動データパネルにできます。
3 つの機能は連携します。Watch がデータを収集し、iPhone がリアルタイムミラー表示、iPad が大画面での振り返りと分析を担います。1 回のライドで生まれたデータは、すべてのデバイスで利用可能です。
詳細
ミラーリング運動セッション: Watch で収集、iPhone で表示
今回のアップデートで最も重要な新 API です。Watch 上で HKWorkoutSession がプライマリセッションとして動作し、startMirroringToCompanionDevice() を呼ぶと、ペアリングされた iPhone が自動的にコンパニオン App を起動し、ミラーセッションを受け取ります(04:15)。
ミラーセッションはプライマリセッションのすべての状態変更 — 開始、一時停止、再開、終了 — を同期します。両端の状態を手動管理する必要はありません。
Watch 側でミラーリングを開始:
// Apple Watch — ワークアウトを開始してミラーリングを有効化
let configuration = HKWorkoutConfiguration()
configuration.activityType = .cycling
configuration.locationType = .outdoor
let session = HKWorkoutSession(
healthStore: healthStore,
configuration: configuration
)
let builder = session.associatedWorkoutBuilder()
builder.dataSource = HKLiveWorkoutDataSource(
healthStore: healthStore,
workoutConfiguration: configuration
)
// ワークアウトを開始
session.startActivity(with: Date())
builder.beginCollection(withStart: Date()) { success, error in
// データ収集が開始済み
}
// iPhone へのミラーリングを有効化
try await healthStore.startMirroringToCompanionDevice(session)
キーポイント:
HKWorkoutConfigurationで運動タイプと場所(屋外/屋内)を定義し、GPS や高度の収集方法に影響します。HKLiveWorkoutDataSourceは Watch 側のリアルタイムデータ入口で、Watch センサーとペアリング済み Bluetooth デバイスから自動収集します。startMirroringToCompanionDevice()は Watch 側でのみ呼べます。コンパニオン iPhone App のインストールと同一 iCloud アカウントが必要です。- Watch App はフォアグラウンドにいる間、ミラーリングが継続します。
iPhone 側では、アンカークエリでリアルタイムデータを受信:
// iPhone — ミラーリングデータを受信
class MirroringManager: ObservableObject {
let healthStore = HKHealthStore()
@Published var heartRate: Double = 0
func observeMirroredData() {
let heartRateType = HKObjectType.quantityType(forIdentifier: .heartRate)!
let query = HKAnchoredObjectQuery(
type: heartRateType,
predicate: nil,
anchor: nil,
limit: HKObjectQueryNoLimit
) { _, samples, _, _, _ in
guard let quantitySamples = samples as? [HKQuantitySample] else { return }
for sample in quantitySamples {
let bpm = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
DispatchQueue.main.async {
self.heartRate = bpm
}
}
}
healthStore.execute(query)
}
}
キーポイント:
HKAnchoredObjectQueryは新データ到着時にコールバック — ポーリング不要です。- ミラーセッション開始後、iPhone App はバックグラウンドで起動され、セッション設定に約 10 秒使えます。
- HealthKit クエリ結果はすべてバックグラウンドスレッドで返るため、UI 更新前にメインスレッドへ切り替えてください。
Watch と iPhone は sendData でカスタムデータも相互送信できます(06:30):
// Watch から iPhone へカスタムイベントを送信
let lapData = "lapCompleted".data(using: .utf8)!
try await healthStore.sendData(lapData, toRemoteWorkoutSession: session)
// iPhone 側で受信 — HKWorkoutSessionDelegate 経由
func workoutSession(_ workoutSession: HKWorkoutSession,
didReceiveDataFromRemoteWorkoutSession data: [Data]) {
for item in data {
if let message = String(data: item, encoding: .utf8) {
print("Watch からのメッセージを受信: \(message)")
}
}
}
キーポイント:
sendDataは両端間で任意のバイナリデータを送れます — ラップマーカー、インターバル切り替え、ユーザー通知などのカスタムイベント向け。- 受信側は
HKWorkoutSessionDelegateのdidReceiveDataFromRemoteWorkoutSessionでデータを取得します。 - 高頻度データチャンネルではありません。連続センサーストリームではなく、少量のイベント級データ向けです。
サイクリングデータ型: センサー統合を自前で書く必要がない
watchOS 10 では HealthKit に 4 つのサイクリング専用データ型が追加されました(08:20):
| データ型 | HealthKit Identifier | 単位 |
|---|---|---|
| ケイデンス | cyclingCadence | 回/分 (count/min) |
| パワー | cyclingPower | ワット (W) |
| 速度 | cyclingSpeed | m/秒 (m/s) |
| FTP | cyclingFunctionalThresholdPower | ワット (W) |
Apple Watch は Bluetooth サイクリングセンサー(パワーメーター、ケイデンスセンサー)に直接接続し、データを自動収集して HealthKit に書き込めます。開発者は Bluetooth コードを 1 行も書く必要がありません。
データソース設定時に、必要な型を有効化します:
let dataSource = HKLiveWorkoutDataSource(
healthStore: healthStore,
workoutConfiguration: configuration
)
// ケイデンス収集を有効化
dataSource.enableCollection(
for: HKQuantityType(.cyclingCadence),
predicate: nil
)
// パワー収集を有効化
dataSource.enableCollection(
for: HKQuantityType(.cyclingPower),
predicate: nil
)
// 速度収集を有効化
dataSource.enableCollection(
for: HKQuantityType(.cyclingSpeed),
predicate: nil
)
キーポイント:
enableCollectionでHKLiveWorkoutDataSourceに指定型の収集を開始させます。呼ばないと、センサーが接続されていても記録されません。- Watch が Bluetooth センサーのペアリングと読み取りを処理します。ペアリングはシステム設定で行い、App の介入は不要です。
- 認可リクエスト時、これらの型を
typesToReadに追加してください。
HKWorkoutBuilder の addSamplesHandler でサイクリングデータをリアルタイム処理(10:45):
builder.addSamplesHandler = { [weak self] samples in
for sample in samples {
switch sample.sampleType {
case HKQuantityType(.cyclingCadence):
if let quantity = (sample as? HKQuantitySample)?.quantity {
let rpm = quantity.doubleValue(for: HKUnit(from: "count/min"))
self?.cadence = Int(rpm)
}
case HKQuantityType(.cyclingPower):
if let quantity = (sample as? HKQuantitySample)?.quantity {
let watts = quantity.doubleValue(for: .watt())
self?.power = Int(watts)
}
case HKQuantityType(.cyclingSpeed):
if let quantity = (sample as? HKQuantitySample)?.quantity {
let mps = quantity.doubleValue(for: HKUnit.meter().unitDivided(by: .second()))
self?.speed = mps
}
default:
break
}
}
}
キーポイント:
addSamplesHandlerは新サンプルごとにコールバック — リアルタイム UI 更新に適しています。- 各
HKQuantitySampleには値とタイムスタンプがあり、doubleValue(for:)で必要な単位に変換できます。 - FTP は HealthKit が複数回のライドデータから自動推定 — ユーザーが 20 分全力テストを手動で行う必要はありません。
[weak self]で循環参照を避けます — builder が handler を保持するため、handler は builder を保持するオブジェクトを強参照すべきではありません。
iPad 向け HealthKit: 大画面の健康データパネル
iPadOS 17 で初めて HealthKit サポートと Health App が導入されました。iPad に心拍センサーや GPS はありませんが、iCloud 同期で他デバイスの健康データを取得できます(14:10)。
iPad で HealthKit 認可をリクエストする方法は iPhone と異なり、HealthKitUI フレームワークが必要です:
import HealthKitUI
import SwiftUI
struct WorkoutView: View {
let healthStore = HKHealthStore()
let typesToShare: Set<HKSampleType> = [.workoutType()]
let typesToRead: Set<HKObjectType> = [
HKObjectType.quantityType(forIdentifier: .cyclingCadence)!,
HKObjectType.quantityType(forIdentifier: .cyclingPower)!,
HKObjectType.quantityType(forIdentifier: .cyclingSpeed)!,
]
var body: some View {
VStack {
// ワークアウトデータ表示 UI
}
.healthDataAccessRequest(
store: healthStore,
shareTypes: typesToShare,
readTypes: typesToRead,
trigger: true,
completion: { result in
switch result {
case .success:
print("認可に成功")
case .failure(let error):
print("認可に失敗: \(error)")
}
}
)
}
}
キーポイント:
- iPad の HealthKit データは iCloud 同期由来で、リアルタイム収集ではありません — 運動後の振り返りと分析向けです。
- 認可 UI は
HealthKitUIのhealthDataAccessRequest(SwiftUI)またはHKHealthStoreのauthorizationViewControllerPresenter(UIKit)を使う必要があります。 - iPad ではマルチウィンドウに対応 — 異なるウィンドウから認可がトリガーされる可能性があります。
- iPadOS 17 より前の iPad では
HKHealthStore.isHealthDataAvailable()がfalseを返すため、バージョンチェックが必要です。
iPad で過去のサイクリングデータをクエリ:
let predicate = HKQuery.predicateForWorkouts(with: .cycling)
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierStartDate,
ascending: false
)
let query = HKSampleQuery(
sampleType: .workoutType(),
predicate: predicate,
limit: 1,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
guard let workout = samples?.first as? HKWorkout else { return }
let distance = workout.totalDistance?.doubleValue(
for: HKUnit.meterUnit(with: .kilo)
)
let energy = workout.totalEnergyBurned?.doubleValue(
for: .kilocalorie()
)
DispatchQueue.main.async {
self.distance = distance
self.calories = energy
}
}
healthStore.execute(query)
キーポイント:
predicateForWorkouts(with:)で運動タイプをフィルタし、そのタイプの workout をすべて返します。HKWorkoutにはtotalDistanceとtotalEnergyBurnedがあり、追加クエリは不要です。- ケイデンス、パワーなど詳細指標は、workout の UUID でフィルタした
HKStatisticsQueryを使います。
重要ポイント
1. ハンドルバー用 iPhone ダッシュボードを作る
やること: iPhone App をライド中のリアルタイムダッシュボードに。ミラーセッションで Watch からケイデンス、パワー、速度、心拍を取得し、ハンドルに固定した iPhone の大画面・大文字で表示。
価値: startMirroringToCompanionDevice() により iPhone が Watch センサーデータにほぼ遅延なくアクセスできます。HKLiveWorkoutDataSource の enableCollection と組み合わせれば、サイクリング指標はすべて自動で揃い、センサー統合コードは不要です。
始め方: Watch 側で .cycling 型の HKWorkoutSession を作成し、startMirroringToCompanionDevice() を呼ぶ。iPhone 側は HKAnchoredObjectQuery でリアルタイムデータを監視し、SwiftUI の大きな Text と Gauge で各指標を表示。
2. 室内サイクリング用 iPad トレーニングパネルを作る
やること: iPad App で最近のサイクリングデータトレンド — FTP 変化曲線、パワーゾーン分布、ケイデンス安定性 — を表示し、Apple Watch がリアルタイム収集したデータに基づくトレーニング指導を提供。
価値: iPad の大画面は複雑なデータ表示に向いています。iPadOS 17 以降の HealthKit では、Watch が収集したサイクリングデータが iCloud 経由で iPad に自動同期されます。FTP はシステムが自動推定 — ユーザーが手入力する必要はありません。
始め方: iPad で HKSampleQuery により .cycling 型の過去 workout をクエリし、cyclingPower、cyclingCadence、cyclingFunctionalThresholdPower を抽出。Swift Charts でパワー曲線と FTP トレンドを描画。
3. サイクリングソーシャル App のデータ収集層として HealthKit を使う
やること: HealthKit のサイクリングデータをソーシャル App のデータソースに。各ライドのルート、パワー、速度を自動記録し、手入力やサードパーティセンサー App に依存しない。
価値: 以前のサイクリング App はセンサー統合を自前で行うか、Strava など第三方データに依存していました。HealthKit がケイデンス、パワー、速度を直接提供するため、ソーシャル機能に集中でき、収集層はシステムに任せられます。
始め方: .cyclingCadence、.cyclingPower、.cyclingSpeed の読み取り権限をリクエスト。HKStatisticsCollectionQuery で日/週単位に集計。HKWorkoutRouteBuilder で GPS ルートを関連付け。プライバシーに注意 — ユーザーが明示的に認可した後だけデータを読み取る。
4. FTP 自動追跡ツールを作る
やること: HealthKit が自動推定する FTP 値の長期変化を追跡し、FTP が向上したときに通知とモチベーションを提供。
価値: FTP はサイクリング能力の最も重要な指標ですが、従来は 20 分全力ライドテストが必要 — 苦痛で不正確です。HealthKit は日常ライドから FTP を推定するため、より現実的で継続的なデータが得られます。
始め方: HKAnchoredObjectQuery で cyclingFunctionalThresholdPower の変化を監視し、各更新の時刻と値を記録。Swift Charts で長期トレンドを描画。FTP が新記録を更新したときにローカル通知を送る。
関連セッション
- Meet watchOS 10 — watchOS 10 の全体アップデート。新デザイン言語と Smart Stack。本 Session の Watch 側 API を理解する前提
- Update your app for watchOS 10 — 既存 watchOS App を watchOS 10 に移行する実践ガイド。SwiftUI 新コンポーネントとレイアウトパターンをカバー
- Platforms State of the Union — WWDC 2023 プラットフォーム技術総覧。HealthKit 更新が各プラットフォームエコシステムのどこに位置するか
コメント
GitHub Issues · utterances