View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

WWDC25に戻る

  • 概要
  • トランスクリプト
  • コード
  • iOSとiPadOSでのHealthKitによるワークアウトのトラッキング

    iOS上で最適なワークアウト体験を構築するためのベストプラクティスを紹介します。ワークアウトセッションのライフサイクル、Apple WatchとiPhoneの間のワークアウトの差異、アプリのロック画面の体験を向上させるためのライブアクティビティとSiriの利用方法について学ぶことができます。

    関連する章

    • 0:00 - イントロダクション
    • 0:56 - ワークアウトセッションの実行
    • 2:50 - セッションメトリックスの取得
    • 8:35 - クラッシュからのリカバリ
    • 9:34 - ベストプラクティス

    リソース

    • Building a multidevice workout app
    • Building a workout app for iPhone and iPad
    • Handling Workout Requests with SiriKit
    • HKWorkoutSession
    • Running workout sessions
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC25

    • HealthKit Medications APIの紹介

    WWDC24

    • App Intentでアプリのコア機能をユーザーに提供

    WWDC23

    • マルチデバイスワークアウトアプリの構築
    • ActivityKitについて
  • このビデオを検索

    こんにちは HealthKitチームのエンジニア Brianです 世の中には 健康増進や 健康維持を目的とした ヘルスアプリやフィットネスアプリは 沢山あります ユーザーの許可を得て これらアプリは HealthKitの 一元化された暗号化データベースや パワフルなAPIにアクセスして 総合的な健康状態を提供します ワークアウトAPIは HealthKitが提供する 最も強力な機能の一つに成長しました 本日は iPhoneとiPadで APIを使用する方法をご紹介します まず ワークアウトセッション実行に関する 基本事項を説明します 次に ワークアウト中の測定値への アクセス方法を 詳しく説明します Apple Watchのプロセスとの違いにも 注目してみます 次に クラッシュが発生した場合の 回復方法を紹介します 最後に ワークアウトのヒントや ベストプラクティスを紹介します ワークアウトセッションを始めましょう すでにApple Watchのアプリで ワークアウトを実行している場合は iPhoneとiPadでも最小限の変更だけで 同じコードを使用できます Apple Watchと同様 ワークアウトセッションで あらゆるアクティビティをトラッキングし 関連付けしたWorkout Builderによる HealthKitへの保存が可能になりました Apple Watchアプリを これから開発される皆さんのために ワークアウトセッション実行方法の例を 簡単に説明します ワークアウトセッションは セットアップから開始、測定値の収集まで 複数のステップに分かれ 最後にセッション終了です まず ワークアウト構成を作成して ユーザーが実行したいと思っている アクティビティを反映させて タイプを設定します 「running」を使用して 場所を「outdoor」に設定します HKWorkoutSessionを作成して 設定を渡します

    ワークアウトセッションから 関連付けられているビルダーを取得して データソースをアタッチします

    セッションで「prepare」を呼び出し 3秒のカウントダウンを表示して デバイス上のセンサーがオンになるまで または外部心拍計を接続するまでの 時間を確保します カウントダウンがあることで ワークアウトアクティビティの開始後すぐに 測定値が利用可能になります カウントダウンが完了したら セッションでstartActivityを 関連するWorkout Builderで beginCollectionを呼び出すだけです アンカーオブジェクトクエリを使用して UIを更新する必要はありません 新しいデータを収集すると Workout Builderデリゲートは アップデートをアプリに提供し ワークアウトの保存時には 測定値を同期処理します

    アプリを使用しているユーザーが ワークアウトを終了すると判断した場合は ライブビルダーで 最終的な測定値を収集できるよう セッションでstopActivityを 呼び出す必要があります

    セッションが停止状態に遷移したら ビルダーで「endCollection」を呼び出して ワークアウトを終了できます ビルダーが完成したら セッションで「end」を呼び出し ワークアウトの概要を表示します

    ワークアウトの実行方法を 一通りご紹介したところで Apple Watchで実行する場合と iPhoneやiPadで実行する場合の 違いについて説明します 第一の 主な違いは 利用可能なセンサーです

    iPhoneとiPadでは すべてのワークアウト アクティビティタイプを利用できますが これらデバイスには 心拍センサーが搭載されていません

    ただし ウェアラブル心拍モニタや Powerbeats Pro 2など ワークアウト中に装着する GATプロファイル対応デバイスとの ペアリングは可能です

    デバイスをペアリングすると HealthKitは心拍数データを デバイスから取得して サンプルとしてHealth Storeに保存し アプリで利用できる状態にします つまり ワークアウト中に 収集したいサンプルと システムが生成できるサンプルは 違っている可能性が あるということです その違いを見ていきましょう

    先ほど 型を生成しました カロリーや距離など ワークアウト中に システムによって生成される データ型です 収集される型のほうは リアルタイムで監視して ワークアウトサンプルに追加する 測定値です たとえば ワークアウト中の 水分摂取量を収集する場合 アプリはHealthデータベースに サンプルを追加する必要があります

    iPhoneやiPadの場合 初期化すると データソースで収集する型には 実行中のアクティビティで収集しそうな すべてのサンプルタイプが含まれます たとえば 外部心拍センサーがない場合 システムで生成しない場合でも 心拍数が含まれます

    データソースは システムが生成したものであるか アプリが保存したものかを問わず 収集したサンプルとタイプを監視して ライブビルダーに渡します

    システムが実際に生成している データを知りたい場合は UIの測定値更新に 使用しているものと同じ Workout Builderデリゲートを 使用できます

    アプリで収集する デフォルトの型を変更する場合は データソースで型のメソッドに対して 「enable collection」や 「disable collection」を呼び出して 必要な型の追加や削除ができます

    たとえば ワークアウト中の 水分摂取量を収集すると アプリが Enable Collectionを呼び出し ワークアウトの実行中に 測定値をサンプルとして Healthデータベースに追加し データソースはサンプルを自動的に ライブビルダーに渡します セッションの測定値を 取得する方法を紹介しました ここで ワークアウトの保存後に 測定値を読み取る方法を 簡単に説明します

    まず ワークアウトオブジェクトの 統計値を使用して 概要を表示します

    ワークアウト実行中の測定値を グラフにしたい場合は 任意の間隔を指定して 統計データ収集クエリを 使用します

    詳細なデータが必要な場合は ワークアウトに関連する数量サンプルは 粒度が1以上になる場合もあることを 忘れないようにしましょう つまり サンプルが より細かい粒度のデータを持っていて HKQuantitySeriesSampleQueryで アクセスできる ということです

    お気づきでしょうが これは 過去のApple Watch ワークアウトについての話でした ただしこれは iOSに保存されている ワークアウトにも当てはまります ここまで アプリの表示中に 測定値を収集して ワークアウト保存後に 読み取る方法を紹介しました もう1つの重要な相違点ですが Apple Watchとは異なり ワークアウト実行中 iPhoneは 高い確率でロックされます プライバシー上の理由から 通常 デバイスのロック中は 健康データを利用できませんが 心配は要りません 1回目のワークアウトセッション スタート時に システムは デバイスのロック中も アプリがワークアウトデータを 利用できることを示す プロンプトを提示します アプリでライブアクティビティを表示する 絶好の機会です iPhoneのロックを解除することなく 最も重要な測定値を ロック画面に表示して 最新情報をリアルタイムで 確認できるようになります 先ほど プライバシープロンプトについて お話ししました データにアクセスできない場合や デバイスがロックされているとき 心拍数データが利用できない場合は UIを更新して 測定値を非表示にし ワークアウトの時間だけを 表示させることができます

    ロック画面からできることは 他にもあります ロック画面もSiri対応に なったことを嬉しく思います iPhoneのロックを解除しなくとも ワークアウトの開始、一時停止、再開と キャンセルが可能になりました ロックが解除されると HealthKitがワークアウトを保存して Health Store経由で アプリが利用できるようにします Siri Intentをアプリに追加して ロック画面からでも 機能させる方法を説明します

    まず Intentハンドラの定義から スタートします ロック画面でも機能させるには アプリ内部からの処理が必要です 次に サポートするIntentを 定義します 読みやすくするために この例では分割しています 次に 引数として渡されるIntentです この例では StartWorkoutIntentです まず 実行中のワークアウトについて確認し ワークアウトがある場合は 「failure」を返します 次に アクティビティ型と場所を 取得する必要があります この例では 屋外でのランニングに設定します 成功レスポンスを返します

    Intentを定義したら 応答できるように Appデリゲートを 作成する必要があります 次に アプリ内のデリゲートを 定義します

    これで アプリがロック画面での Siri Workout Intentに対応します

    Siri Intentとライブアクティビティを 追加しておくと デバイスの状態に 左右されることなく ユーザーがアプリを最大限に 活用できるようになります これら2つの技術の詳細については WWDC2024の「Bring your app's core features to users with App Intents」と WWDC23の「Meet ActivityKit」をご覧ください セッションの測定値を取得する方法と ライブアクティビティで Siri Intentの使用方法が 分かったところで Crash Recoveryに進みましょう Crash Recoveryは以前から Watchにあった機能ですが 重要なポイントを3つ確認しておきましょう クラッシュ発生時 システムは自動的に アプリを再起動させます Builderのワークアウトセッションは 再起動前の状態に戻ります ただし ライブデータソースの設定は やり直しが必要になります iPhoneとiPad用に 新しく セッション中のワークアウトの 回復に使用する シーンデリゲートを追加しました

    Siriインテント用に作成した アプリデリゲートを使用して クラッシュからのリカバリ処理用の シーンデリゲートを追加できます その場合は Appデリゲートを定義して optionsパラメータが shouldHandleActiveWorkoutRecovery とあるか確認します 次に 復元された recoveredSessionを Health Storeから受け取って WorkoutManagerに 渡すよう指定します WorkoutManagerから アクティブなワークアウトを 継続するよう処理できます そう 後はdataSourceを 作成し直すだけです

    最後になりますが ワークアウトの ベストプラクティスをご紹介します 測定値をすべて取得できるよう Watchアプリがあればそこで ワークアウトを開始するようにします Health StoreからStart Watch Appを呼び出すだけです その後は必ず ワークアウトを iPhoneにミラーリングします WWDC2023の「Build a multi-device workout app」で 詳しく紹介しています

    承認リクエストは 必要なデータ型についてのみ 実行させるようにします アプリの本質とは無関係に見える データ型で承認を要求して ユーザーに疑念を持たせてしまうのは 得策ではありません 最後に ワークアウトの作成と 保存には Workout Builder APIを 使用するようにします これで アクティビティリングが 適切に更新されるようになります

    以上が iPhoneやiPadでの HealthKitによる ワークアウトのトラッキング方法です アップデートを機に これらデバイス用の 堅牢なAPIが搭載され クラッシュからのリカバリ機能と ワークアウト表示の管理機能が加わり デバイスのロック中も操作できます ここで お願いがあります 本セッション用のデモアプリは 是非ともダウンロードをお願いします 本日紹介したすべてのコードの機能を すぐに試していただけるよう ラップされています すでにiPhoneまたはiPad用の アプリを構築されている場合は 本日紹介した Workout Builder APIへの アップグレードを強くお勧めします 改善ポイントを 気に入っていただけると思います Apple Watch用のアプリを 構築されている場合は 同じAPIでマルチプラットフォームに 対応することで 非Apple Watchユーザー向けの まったく新しい市場が開けてきます 最後に 実装した機能は フィードバックから生まれました 皆さんからの引き続きのご提供を 心よりお待ちしています 世界を健康的にするための アプリ構築に必要な機能を提供して これからも皆さんをサポートします ご視聴ありがとうございました

    • 1:30 - Set up workout session

      // Set up workout session
      
      // Create workout configuration
      let configuration = HKWorkoutConfiguration()
      configuration.activityType = .running
      configuration.locationType = .outdoor
      
      // Create workout session
      let session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
      session.delegate = self
      
      // Get associated workout builder and add data source
      let builder = session.associatedWorkoutBuilder()
      builder.delegate = self
      builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
                                                   workoutConfiguration: configuration)
    • 1:54 - Starting the session

      // Prepare and start session
      
      session.prepare()
      
      // Start and display count down
      
      // Start session and builder collection once count down finishes
      session.startActivity(with: startDate)
      try await builder.beginCollection(at: startDate)
    • 2:14 - Handling Metrics

      // Handling collected metrics
      
      func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, 
                          didCollectDataOf collectedTypes: Set<HKSampleType>) {
          for type in collectedTypes {
              guard let quantityType = type as? HKQuantityType else { return }
      
              let statistics = workoutBuilder.statistics(for: quantityType)
      
              // Update the published values
              updateForStatistics(statistics)
          }
      }
    • 2:28 - Ending workout

      // Stopping the workout session
      
      session.stopActivity(with: .now)
      
      // Session transitions to stopped then call end
      func workoutSession(_ workoutSession: HKWorkoutSession,
                          didChangeTo toState: HKWorkoutSessionState,
                          from fromState: HKWorkoutSessionState,
                          date: Date) {
          guard change.newState == .stopped, let builder else { return }
           
          try await builder.endCollection(at: change.date)
          let finishedWorkout = try await builder.finishWorkout()
          session.end()
      }
    • 7:17 - Set up Siri Intent

      // Create an INExtension within your main app
      
      // Define an intent handler
      public class IntentHandler: INExtension {
      
      }
      
      // Define the intents to support
      extension IntentHandler: INStartWorkoutIntentHandling
      
      extension IntentHandler: INPauseWorkoutIntentHandling
      
      extension IntentHandler: INResumeWorkoutIntentHandling
      
      extension IntentHandler: INEndWorkoutIntentHandling
    • 7:32 - Handle the Siri intent

      // Handle the intent
      
      public func handle(intent: INStartWorkoutIntent) async -> INStartWorkoutIntentResponse {
          let state = await WorkoutManager.shared.state
              
          switch state {
          case .running, .paused, .prepared, .stopped:
              return INStartWorkoutIntentResponse(code: .failureOngoingWorkout, 
                                                  userActivity: nil)
          default:
              break;
          }
          Task {
              await MainActor.run {
                  // Handle the intents activity type and location
                  WorkoutManager.shared.setWorkoutConfiguration(activityType: .running,   
                                                                location: .outdoor)
              }
          }
          return INStartWorkoutIntentResponse(code: .success, userActivity: nil)
       }
    • 7:52 - App Delegate

      // Implement an app delegate
      
      // Create app delegate
      class WorkoutsOniOSSampleAppDelegate: NSObject, UIApplicationDelegate {
          let handler = IntentHandler()
      
          func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? {
              return handler
          }
      }
      
      // Add app delegate to app
      struct WorkoutsOniOSSampleApp: App {
          @UIApplicationDelegateAdaptor(WorkoutsOniOSSampleAppDelegate.self) var appDelegate
      
      }
    • 9:09 - Set up crash recovery

      // App Delegate
      
      func application(_ application: UIApplication,
                       configurationForConnecting connectingSceneSession: UISceneSession,
                       options: UIScene.ConnectionOptions) -> UISceneConfiguration {
          if options.shouldHandleActiveWorkoutRecovery {
              let store = HKHealthStore()
              store.recoverActiveWorkoutSession(completion: { (workoutSession, error) in
                  // Handle error
                  Task {
                      await WorkoutManager.shared.recoverWorkout(recoveredSession: workoutSession)
                  }
              })
          }
          let configuration = UISceneConfiguration(name: "Default Configuration", 
                                                   sessionRole: connectingSceneSession.role)
          configuration.delegateClass = WorkoutsOniOSSampleAppSceneDelegate.self
          return configuration
      }
    • 9:25 - Recover the workout session

      // Recover the workout for the session
      
      
      func recoverWorkout(recoveredSession: HKWorkoutSession) {
          session = recoveredSession
          builder = recoveredSession.associatedWorkoutBuilder()
          session?.delegate = self
          builder?.delegate = self
          workoutConfiguration = recoveredSession.workoutConfiguration
      
          let dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,                                                                  
                                                   workoutConfiguration: workoutConfiguration)
          builder?.dataSource = dataSource
      }

Developer Footer

  • ビデオ
  • WWDC25
  • iOSとiPadOSでのHealthKitによるワークアウトのトラッキング
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習とAI
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン