View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Todos os vídeos
  • Sobre

Mais vídeos

  • Sobre
  • Código
  • Build a workout app for Apple Watch

    Build a workout app from scratch using SwiftUI and HealthKit during this code along. Learn how to support the Always On state using timelines to update workout metrics. Follow best design practices for workout apps.

    Recursos

    • Build a workout app for Apple Watch
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC23

    • Build a multi-device workout app

    WWDC21

    • What's new in SwiftUI
    • What's new in watchOS 8
  • Buscar neste vídeo...
    • 3:17 - StartView - import HealthKit

      import HealthKit
    • 3:25 - StartView - workoutTypes

      var workoutTypes: [HKWorkoutActivityType] = [.cycling, .running, .walking]
    • 3:26 - StartView - HKWorkoutActivityType identifiable and name

      extension HKWorkoutActivityType: Identifiable {
          public var id: UInt {
              rawValue
          }
      
          var name: String {
              switch self {
              case .running:
                  return "Run"
              case .cycling:
                  return "Bike"
              case .walking:
                  return "Walk"
              default:
                  return ""
              }
          }
      }
    • 4:22 - StartView - body

      List(workoutTypes) { workoutType in
          NavigationLink(
              workoutType.name,
              destination: Text(workoutType.name)
          ).padding(
              EdgeInsets(top: 15, leading: 5, bottom: 15, trailing: 5)
          )
      }
      .listStyle(.carousel)
      .navigationBarTitle("Workouts")
    • 6:55 - SessionPagingView - Tab enum and selection

      @State private var selection: Tab = .metrics
      
      enum Tab {
          case controls, metrics, nowPlaying
      }
    • 7:20 - SessionPagingView - TabView

      TabView(selection: $selection) {
          Text("Controls").tag(Tab.controls)
          Text("Metrics").tag(Tab.metrics)
          Text("Now Playing").tag(Tab.nowPlaying)
      }
    • 9:02 - MetricsView - VStack and TextViews

      VStack(alignment: .leading) {
          Text("03:15.23")
              .foregroundColor(Color.yellow)
              .fontWeight(.semibold)
          Text(
              Measurement(
                  value: 47,
                  unit: UnitEnergy.kilocalories
              ).formatted(
                  .measurement(
                      width: .abbreviated,
                      usage: .workout,
                      numberFormat: .numeric(precision: .fractionLength(0))
                  )
              )
          )
          Text(
              153.formatted(
                  .number.precision(.fractionLength(0))
              )
              + " bpm"
          )
          Text(
              Measurement(
                  value: 515,
                  unit: UnitLength.meters
              ).formatted(
                  .measurement(
                      width: .abbreviated,
                      usage: .road
                  )
              )
          )
      }
      .font(.system(.title, design: .rounded)
              .monospacedDigit()
              .lowercaseSmallCaps()
      )
      .frame(maxWidth: .infinity, alignment: .leading)
      .ignoresSafeArea(edges: .bottom)
      .scenePadding()
    • 11:42 - ElapsedTimeView - ElapsedTimeView and ElapsedTimeFormatter

      struct ElapsedTimeView: View {
          var elapsedTime: TimeInterval = 0
          var showSubseconds: Bool = true
          @State private var timeFormatter = ElapsedTimeFormatter()
      
          var body: some View {
              Text(NSNumber(value: elapsedTime), formatter: timeFormatter)
                  .fontWeight(.semibold)
                  .onChange(of: showSubseconds) {
                      timeFormatter.showSubseconds = $0
                  }
          }
      }
      
      class ElapsedTimeFormatter: Formatter {
          let componentsFormatter: DateComponentsFormatter = {
              let formatter = DateComponentsFormatter()
              formatter.allowedUnits = [.minute, .second]
              formatter.zeroFormattingBehavior = .pad
              return formatter
          }()
          var showSubseconds = true
      
          override func string(for value: Any?) -> String? {
              guard let time = value as? TimeInterval else {
                  return nil
              }
      
              guard let formattedString = componentsFormatter.string(from: time) else {
                  return nil
              }
      
              if showSubseconds {
                  let hundredths = Int((time.truncatingRemainder(dividingBy: 1)) * 100)
                  let decimalSeparator = Locale.current.decimalSeparator ?? "."
                  return String(format: "%@%@%0.2d", formattedString, decimalSeparator, hundredths)
              }
      
              return formattedString
          }
      }
    • 13:56 - MetricsView - replace TextView with ElapsedTimeView

      ElapsedTimeView(
          elapsedTime: 3 * 60 + 15.24,
          showSubseconds: true
      ).foregroundColor(Color.yellow)
    • 14:47 - ControlsView - Stacks, Buttons and TextViews

      HStack {
          VStack {
              Button {
              } label: {
                  Image(systemName: "xmark")
              }
              .tint(Color.red)
              .font(.title2)
              Text("End")
          }
          VStack {
              Button {
              } label: {
                  Image(systemName: "pause")
              }
              .tint(Color.yellow)
              .font(.title2)
              Text("Pause")
          }
      }
    • 16:05 - SessionPagingView - import WatchKit

      import WatchKit
    • 16:09 - SessionPagingView - TabView using actual views

      ControlsView().tag(Tab.controls)
      MetricsView().tag(Tab.metrics)
      NowPlayingView().tag(Tab.nowPlaying)
    • 17:08 - StartView - NavigationLink to use SessionPagingView

      destination: SessionPagingView()
    • 17:50 - SummaryView - SummaryMetricView

      struct SummaryMetricView: View {
          var title: String
          var value: String
      
          var body: some View {
              Text(title)
              Text(value)
                  .font(.system(.title2, design: .rounded)
                          .lowercaseSmallCaps()
                  )
                  .foregroundColor(.accentColor)
              Divider()
          }
      }
    • 18:27 - SummaryView - durationFormatter

      @State private var durationFormatter: DateComponentsFormatter = {
          let formatter = DateComponentsFormatter()
          formatter.allowedUnits = [.hour, .minute, .second]
          formatter.zeroFormattingBehavior = .pad
          return formatter
      }()
    • 18:45 - SummaryView - body

      ScrollView(.vertical) {
          VStack(alignment: .leading) {
              SummaryMetricView(
                  title: "Total Time",
                  value: durationFormatter.string(from: 30 * 60 + 15) ?? ""
              ).accentColor(Color.yellow)
              SummaryMetricView(
                  title: "Total Distance",
                  value: Measurement(
                      value: 1625,
                      unit: UnitLength.meters
                  ).formatted(
                      .measurement(
                          width: .abbreviated,
                          usage: .road
                      )
                  )
              ).accentColor(Color.green)
              SummaryMetricView(
                  title: "Total Energy",
                  value: Measurement(
                      value: 96,
                      unit: UnitEnergy.kilocalories
                  ).formatted(
                      .measurement(
                          width: .abbreviated,
                          usage: .workout,
                          numberFormat: .numeric(precision: .fractionLength(0))
                      )
                  )
              ).accentColor(Color.pink)
              SummaryMetricView(
                  title: "Avg. Heart Rate",
                  value: 143
                      .formatted(
                          .number.precision(.fractionLength(0))
                      )
                  + " bpm"
              ).accentColor(Color.red)
              Button("Done") {
              }
          }
          .scenePadding()
      }
      .navigationTitle("Summary")
      .navigationBarTitleDisplayMode(.inline)
    • 21:00 - ActivityRingsView

      import HealthKit
      import SwiftUI
      
      struct ActivityRingsView: WKInterfaceObjectRepresentable {
          let healthStore: HKHealthStore
      
          func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject {
              let activityRingsObject = WKInterfaceActivityRing()
      
              let calendar = Calendar.current
              var components = calendar.dateComponents([.era, .year, .month, .day], from: Date())
              components.calendar = calendar
      
              let predicate = HKQuery.predicateForActivitySummary(with: components)
      
              let query = HKActivitySummaryQuery(predicate: predicate) { query, summaries, error in
                  DispatchQueue.main.async {
                      activityRingsObject.setActivitySummary(summaries?.first, animated: true)
                  }
              }
      
              healthStore.execute(query)
      
              return activityRingsObject
          }
      
          func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) {
      
          }
      }
    • 22:15 - SummaryView - add ActivityRingsView

      Text("Activity Rings")
      ActivityRingsView(
          healthStore: HKHealthStore()
      ).frame(width: 50, height: 50)
    • 22:28 - SummaryView - import HealthKit

      import HealthKit
    • 25:22 - WorkoutManager

      import HealthKit
      
      class WorkoutManager: NSObject, ObservableObject {
      
      }
    • 25:53 - MyWorkoutsApp - add workoutManager @StateObject

      @StateObject var workoutManager = WorkoutManager()
    • 26:00 - MyWorkoutsApp - .environmentObject to NavigationView

      .environmentObject(workoutManager)
    • 26:25 - WorkoutManager - selectedWorkout

      var selectedWorkout: HKWorkoutActivityType?
    • 26:49 - StartView - add workoutManager

      @EnvironmentObject var workoutManager: WorkoutManager
    • 26:56 - StartView - Add tag and selection to NavigationLink

      ,
      tag: workoutType,
      selection: $workoutManager.selectedWorkout
    • 27:32 - WorkoutManager - Add healthStore, session, builder

      let healthStore = HKHealthStore()
      var session: HKWorkoutSession?
      var builder: HKLiveWorkoutBuilder?
    • 27:42 - WorkoutManager - startWorkout(workoutType:)

      func startWorkout(workoutType: HKWorkoutActivityType) {
          let configuration = HKWorkoutConfiguration()
          configuration.activityType = workoutType
          configuration.locationType = .outdoor
      
          do {
              session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
              builder = session?.associatedWorkoutBuilder()
          } catch {
              // Handle any exceptions.
              return
          }
      
          builder?.dataSource = HKLiveWorkoutDataSource(
              healthStore: healthStore,
              workoutConfiguration: configuration
          )
      
          // 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.
          }
      }
    • 29:06 - WorkoutManager - selectedWorkout didSet

      {
          didSet {
              guard let selectedWorkout = selectedWorkout else { return }
              startWorkout(workoutType: selectedWorkout)
          }
      }
    • 29:35 - WorkoutManager - requestAuthorization from 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.
          }
      }
    • 30:20 - StartView - requestAuthorization onAppear

      .onAppear {
          workoutManager.requestAuthorization()
      }
    • 31:30 - Privacy - Health Share Usage Description - Key

      NSHealthShareUsageDescription
    • 31:38 - Privacy - Health Share Usage Description - Value

      Your workout related data will be used to display your saved workouts in MyWorkouts.
    • 31:47 - Privacy - Health Update Usage Description - Key

      NSHealthUpdateUsageDescription
    • 31:54 - Privacy - Health Update Usage Description - Value

      Workouts tracked by MyWorkouts on Apple Watch will be saved to HealthKit.
    • 33:29 - WorkoutManager - session state control

      // MARK: - State Control
      
      // The workout session state.
      @Published var running = false
      
      func pause() {
          session?.pause()
      }
      
      func resume() {
          session?.resume()
      }
      
      func togglePause() {
          if running == true {
              pause()
          } else {
              resume()
          }
      }
      
      func endWorkout() {
          session?.end()
      }
    • 34:11 - WorkoutManager - HKWorkoutSessionDelegate

      // MARK: - HKWorkoutSessionDelegate
      extension WorkoutManager: HKWorkoutSessionDelegate {
          func workoutSession(_ workoutSession: HKWorkoutSession,
                              didChangeTo toState: HKWorkoutSessionState,
                              from fromState: HKWorkoutSessionState,
                              date: Date) {
              DispatchQueue.main.async {
                  self.running = toState == .running
              }
      
              // Wait for the session to transition states before ending the builder.
              if toState == .ended {
                  builder?.endCollection(withEnd: date) { (success, error) in
                      self.builder?.finishWorkout { (workout, error) in
                      }
                  }
              }
          }
      
          func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
      
          }
      }
    • 34:58 - WorkoutManager - assign HKWorkoutSessionDelegate in startWorkout()

      session?.delegate = self
    • 35:22 - ControlsView - workoutManager environmentObject

      @EnvironmentObject var workoutManager: WorkoutManager
    • 35:33 - ControlsView - End Button action

      workoutManager.endWorkout()
    • 35:43 - ControlsView - Pause / Resume Button and Text

      Button {
          workoutManager.togglePause()
      } label: {
          Image(systemName: workoutManager.running ? "pause" : "play")
      }
      .tint(Color.yellow)
      .font(.title2)
      Text(workoutManager.running ? "Pause" : "Resume")
    • 36:30 - SessionPagingView - add workoutManager environment variable

      @EnvironmentObject var workoutManager: WorkoutManager
    • 36:42 - SessionPagingView - navigationBar

      .navigationTitle(workoutManager.selectedWorkout?.name ?? "")
      .navigationBarBackButtonHidden(true)
      .navigationBarHidden(selection == .nowPlaying)
    • 37:10 - SessionPagingView - onChange of workoutManager.running

      .onChange(of: workoutManager.running) { _ in
              displayMetricsView()
          }
      }
      
      private func displayMetricsView() {
          withAnimation {
              selection = .metrics
          }
      }
    • 37:45 - WorkoutManager - showingSummaryView

      @Published var showingSummaryView: Bool = false {
          didSet {
              // Sheet dismissed
              if showingSummaryView == false {
                  selectedWorkout = nil
              }
          }
      }
    • 37:59 - WorkoutManager - showingSummaryView true in endWorkout

      showingSummaryView = true
    • 38:22 - MyWorkoutApp - add summaryView sheet to NavigationView

      .sheet(isPresented: $workoutManager.showingSummaryView) {
          SummaryView()
      }
    • 38:49 - SummaryView - add dismiss environment variable

      @Environment(\.dismiss) var dismiss
    • 38:58 - SummaryView - add dismiss() to done button

      dismiss()
    • 40:25 - WorkoutManager - Metric publishers

      // MARK: - Workout Metrics
      @Published var averageHeartRate: Double = 0
      @Published var heartRate: Double = 0
      @Published var activeEnergy: Double = 0
      @Published var distance: Double = 0
    • 40:48 - WorkoutManager - assigned as HKLiveWorkoutBuilderDelegate in startWorkout()

      builder?.delegate = self
    • 41:05 - WorkoutManager - add HKLiveWorkoutBuilderDelegate extension

      // MARK: - HKLiveWorkoutBuilderDelegate
      extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
          func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
          }
      
          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)
              }
          }
      }
    • 42:01 - WorkoutManager - add updateForStatistics()

      func updateForStatistics(_ statistics: HKStatistics?) {
          guard let statistics = statistics else { return }
      
          DispatchQueue.main.async {
              switch statistics.quantityType {
              case HKQuantityType.quantityType(forIdentifier: .heartRate):
                  let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
                  self.heartRate = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) ?? 0
                  self.averageHeartRate = statistics.averageQuantity()?.doubleValue(for: heartRateUnit) ?? 0
              case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
                  let energyUnit = HKUnit.kilocalorie()
                  self.activeEnergy = statistics.sumQuantity()?.doubleValue(for: energyUnit) ?? 0
              case HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning), HKQuantityType.quantityType(forIdentifier: .distanceCycling):
                  let meterUnit = HKUnit.meter()
                  self.distance = statistics.sumQuantity()?.doubleValue(for: meterUnit) ?? 0
              default:
                  return
              }
          }
      }
    • 43:25 - MetricsView - add workoutManager as environment variable to MetricsView

      @EnvironmentObject var workoutManager: WorkoutManager
    • 43:35 - MetricsView - VStack with Text bound to workoutManager variables

      VStack(alignment: .leading) {
          ElapsedTimeView(
              elapsedTime: workoutManager.builder?.elapsedTime ?? 0,
              showSubseconds: true
          ).foregroundColor(Color.yellow)
          Text(
              Measurement(
                  value: workoutManager.activeEnergy,
                  unit: UnitEnergy.kilocalories
              ).formatted(
                  .measurement(
                      width: .abbreviated,
                      usage: .workout,
                      numberFormat: .numeric(precision: .fractionLength(0))
                  )
              )
          )
          Text(
              workoutManager.heartRate
                  .formatted(
                      .number.precision(.fractionLength(0))
                  )
              + " bpm"
          )
          Text(
              Measurement(
                  value: workoutManager.distance,
                  unit: UnitLength.meters
              ).formatted(
                  .measurement(
                      width: .abbreviated,
                      usage: .road
                  )
              )
          )
      }
    • 45:51 - MetricsView - MetricsTimelineSchedule

      private struct MetricsTimelineSchedule: TimelineSchedule {
          var startDate: Date
      
          init(from startDate: Date) {
              self.startDate = startDate
          }
      
          func entries(from startDate: Date, mode: TimelineScheduleMode) -> PeriodicTimelineSchedule.Entries {
              PeriodicTimelineSchedule(
                  from: self.startDate,
                  by: (mode == .lowFrequency ? 1.0 : 1.0 / 30.0)
              ).entries(
                  from: startDate,
                  mode: mode
              )
          }
      }
    • 46:38 - MetricsView - TimelineView wrapping VStack

      TimelineView(
          MetricsTimelineSchedule(
              from: workoutManager.builder?.startDate ?? Date()
          )
      ) { context in
          VStack(alignment: .leading) {
              ElapsedTimeView(
                  elapsedTime: workoutManager.builder?.elapsedTime ?? 0,
                  showSubseconds: context.cadence == .live
              ).foregroundColor(Color.yellow)
              Text(
                  Measurement(
                      value: workoutManager.activeEnergy,
                      unit: UnitEnergy.kilocalories
                  ).formatted(
                      .measurement(
                          width: .abbreviated,
                          usage: .workout,
                          numberFormat: .numeric(precision: .fractionLength(0))
                      )
                  )
              )
              Text(
                  workoutManager.heartRate
                      .formatted(
                          .number.precision(.fractionLength(0))
                      )
                  + " bpm"
              )
              Text(
                  Measurement(
                      value: workoutManager.distance,
                      unit: UnitLength.meters
                  ).formatted(
                      .measurement(
                          width: .abbreviated,
                          usage: .road
                      )
                  )
              )
          }
          .font(.system(.title, design: .rounded)
                  .monospacedDigit()
                  .lowercaseSmallCaps()
          )
          .frame(maxWidth: .infinity, alignment: .leading)
          .ignoresSafeArea(edges: .bottom)
          .scenePadding()
      }
    • 48:23 - WorkoutManager - workout: HKWorkout added

      @Published var workout: HKWorkout?
    • 48:38 - WorkoutManager - assign HKWorkout in finishWorkout

      DispatchQueue.main.async {
          self.workout = workout
      }
    • 48:57 - WorkoutManager - resetWorkout()

      func resetWorkout() {
          selectedWorkout = nil
          builder = nil
          session = nil
          workout = nil
          activeEnergy = 0
          averageHeartRate = 0
          heartRate = 0
          distance = 0
      }
    • 49:21 - WorkoutManager - add resetWorkout to showingSummaryView didSet

      resetWorkout()
    • 49:48 - SummaryView - add workoutManager

      @EnvironmentObject var workoutManager: WorkoutManager
    • 50:06 - SummaryView - add ProgressView

      if workoutManager.workout == nil {
          ProgressView("Saving workout")
              .navigationBarHidden(true)
      } else {
          ScrollView(.vertical) {
              VStack(alignment: .leading) {
                  SummaryMetricView(
                      title: "Total Time",
                      value: durationFormatter.string(from: 30 * 60 + 15) ?? ""
                  ).accentColor(Color.yellow)
                  SummaryMetricView(
                      title: "Total Distance",
                      value: Measurement(
                          value: 1625,
                          unit: UnitLength.meters
                      ).formatted(
                          .measurement(
                              width: .abbreviated,
                              usage: .road
                          )
                      )
                  ).accentColor(Color.green)
                  SummaryMetricView(
                      title: "Total Calories",
                      value: Measurement(
                          value: 96,
                          unit: UnitEnergy.kilocalories
                      ).formatted(
                          .measurement(
                              width: .abbreviated,
                              usage: .workout,
                              numberFormat: .numeric(precision: .fractionLength(0))
                          )
                      )
                  ).accentColor(Color.pink)
                  SummaryMetricView(
                      title: "Avg. Heart Rate",
                      value: 143.formatted(
                          .number.precision(.fractionLength(0))
                      )
                      + " bpm"
                  )
                  Text("Activity Rings")
                  ActivityRingsView(healthStore: workoutManager.healthStore)
                      .frame(width: 50, height: 50)
                  Button("Done") {
                      dismiss()
                  }
              }
              .scenePadding()
          }
          .navigationTitle("Summary")
          .navigationBarTitleDisplayMode(.inline)
      }
    • 50:43 - SummaryView - SummaryMetricViews using HKWorkout values

      SummaryMetricView(
          title: "Total Time",
          value: durationFormatter
              .string(from: workoutManager.workout?.duration ?? 0.0) ?? ""
      ).accentColor(Color.yellow)
      SummaryMetricView(
          title: "Total Distance",
          value: Measurement(
              value: workoutManager.workout?.totalDistance?
                  .doubleValue(for: .meter()) ?? 0,
              unit: UnitLength.meters
          ).formatted(
              .measurement(
                  width: .abbreviated,
                  usage: .road
              )
          )
      ).accentColor(Color.green)
      SummaryMetricView(
          title: "Total Energy",
          value: Measurement(
              value: workoutManager.workout?.totalEnergyBurned?
                              .doubleValue(for: .kilocalorie()) ?? 0,
              unit: UnitEnergy.kilocalories
          ).formatted(
              .measurement(
                  width: .abbreviated,
                  usage: .workout,
                  numberFormat: .numeric(precision: .fractionLength(0))
              )
          )
      ).accentColor(Color.pink)
      SummaryMetricView(
          title: "Avg. Heart Rate",
          value: workoutManager.averageHeartRate
              .formatted(
                  .number.precision(.fractionLength(0))
              )
          + " bpm"
      ).accentColor(Color.red)
    • 51:45 - SessionPagingView - add isLuminanceReduced

      @Environment(\.isLuminanceReduced) var isLuminanceReduced
    • 51:57 - SessionPagingView - add tabViewStyle and onChangeOf based on isLuminanceReduced

      .tabViewStyle(
          PageTabViewStyle(indexDisplayMode: isLuminanceReduced ? .never : .automatic)
      )
      .onChange(of: isLuminanceReduced) { _ in
          displayMetricsView()
      }

Developer Footer

  • Vídeos
  • WWDC21
  • Build a workout app for Apple Watch
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines