/WorkoutsTutorial-WatchOS

๐Ÿ•ฐ๏ธ WorkoutsTutorial WatchOS

Primary LanguageSwift

๐Ÿ•ฐ๏ธ WorkoutsTutorial-WatchOS

  • WWDC22) Build a workout app for Apple Watch ์„ธ์…˜์—์„œ ์•Œ์•„๋ณด๋Š” HealthKit ์— ๋Œ€ํ•ด์„œ ๊ธฐ๋กํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
  • HealthKit ์˜ heart rate ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉํ‘œ๋ฅผ ๊ฐ€์ง€๊ณ  ์‹œ์ฒญํ•œ ์„ธ์…˜์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์„ธ์…˜์˜ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

๐Ÿ‘‰ Build a workout app for Apple Watch

์„ธ์…˜ ์ค‘ ์‹ฌ๋ฐ•์ˆ˜๋ฅผ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” HKWorkoutSession ํด๋ž˜์Šค์— ๋Œ€ํ•ด์„œ ๋“ค์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์Šคํฌ๋ฆฐ์ƒท 2022-11-08 ์˜คํ›„ 1 51 06

  • HKWorkoutSession ์€ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์„ ์œ„ํ•ด ์žฅ์น˜์˜ ์„ผ์„œ๋ฅผ ์ค€๋น„ํ•˜๋ฏ€๋กœ, ์šด๋™๊ณผ ๊ด€๋ จ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์นผ๋กœ๋ฆฌ์™€ ์‹ฌ๋ฐ•์ˆ˜์™€ ๊ฐ™์€ ์ •๋ณด ์ˆ˜์ง‘) . ๋˜ํ•œ ์šด๋™์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์„ ๋•Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • HKLiveWorkoutBuilder ๋Š” HKWorkout ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ž๋™์œผ๋กœ ์ƒ˜ํ”Œ๊ณผ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.

**New ways to work with workouts** ์„ธ์…˜์—์„œ ๋งŽ์€ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ request authorization ๋‹จ๊ณ„

/// 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.
        }
    }

// โœ… ๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// .onAppear {
//            workoutManager.requestAuthorization()
//           }

๐Ÿ‘‰ ํ”„๋กœ์ ํŠธ ์„ธํŒ…

  • watch app target ์— HealthKit ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ workout session ์ด ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— background modes capability ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-14 แ„‹แ…ฉแ„’แ…ฎ 10 58 09

  • ํ•ด๋‹น target ์ด HealthKit ์„ ์ง€์›ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    • apple developer ์—์„œ Identifiers ์—์„œ App ID ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • HealthKit ์ง€์›์„ ์„ ํƒํ•˜๊ณ  ๋ฒˆ๋“ค ์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด์„œ App ID ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-11 แ„‹แ…ฉแ„’แ…ฎ 6 13 21

์Šคํฌ๋ฆฐ์ƒท 2022-11-11 ์˜คํ›„ 6 13 02

  • info.plist ์—์„œ ๊ถŒํ•œ์„ ์–ป์„ ๋•Œ ๋ฌธ๊ตฌ๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.
    • NSHealthShareUsageDescription ํ˜น์€ Privacy - Health Share Usage Description key ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.(์•ฑ์ด ์™œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์™€์•ผ ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.)
    • NSHealthUpdateUsageDescription ํ˜น์€ Privacy - Health Update Usage Description key ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.(์•ฑ์—์„œ ์ž‘์„ฑํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.)

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-11 แ„‹แ…ฉแ„’แ…ฎ 6 35 28

๐Ÿ‘‰ ๋นŒ๋“œํ•˜์—ฌ Authorization ํ™•์ธ

  • ๋นŒ๋“œ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (์‹ฌ์žฅ - ์‹ฌ๋ฐ•์ˆ˜ ์˜ ์ฝ๊ธฐ ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)
    • ์šฐ๋ฆฌ๊ฐ€ ์ œ๊ณตํ•œ ์„ค๋ช…๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์šฐ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€ํ•œ ๊ถŒํ•œ์— ๋Œ€ํ•ด์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-14 แ„‹แ…ฉแ„’แ…ฎ 8 47 41

๊ถŒํ•œ์„ ์–ป์—ˆ์œผ๋‹ˆ ์‹ฌ๋ฐ•์ˆ˜์˜ ์ •๋ณด๋ฅผ ์–ป์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‰ heart rate(์‹ฌ๋ฐ•์ˆ˜)

  • builder ์ƒˆ๋กœ์šด ์ƒ˜ํ”Œ์„ ์ˆ˜์ง‘ํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” delegate method ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • statistics ์ค‘ heartRate ์— ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ switch ๋ฌธ์œผ๋กœ ๋Œ€์‘ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
// MARK: - HKLiveWorkoutBuilderDelegate

extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
    /// called whenever the builder collects an events.
    func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
         // ...
    }

    /// โœ… called whenever the builder collects new samples.
    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)
        }
    }
}

// ...

// MARK: - Workout Metrics

    @Published var averageHeartRate: Double = 0
    @Published var heartRate: Double = 0
    
    func updateForStatistics(_ statistics: HKStatistics?) {
        guard let statistics = statistics else { return }

        DispatchQueue.main.async {
            switch statistics.quantityType {
            // โœ… We want beats per minute, so we use a count HKUnit divided by a minute HKUnit.
            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

// ...

            }
        }
    }

โœ… Data flow

data flow ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-08 แ„‹แ…ฉแ„’แ…ฎ 3 45 36

WorkoutManager(์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” HealthKit ์„ ๋งค๋‹ˆ์ง• ํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค)๋Š” HealthKit ๊ณผ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. HKWorkoutSession ๊ณผ ์ธํ„ฐํŽ˜์ด์Šคํ•˜์—ฌ ์šด๋™์„ ์‹œ์ž‘, ์ผ์‹œ ์ค‘์ง€ ๋ฐ ์ข…๋ฃŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. HKLiveWorkoutBuilder ์™€ ์ธํ„ฐํŽ˜์ด์Šคํ•˜์—ฌ ์šด๋™ ์ƒ˜ํ”Œ์„ ์ˆ˜์‹ ํ•˜๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ทฐ์— ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

environmentObject ๋กœ WorkoutManager ๋ฅผ ํ• ๋‹นํ•˜์—ฌ NavigationView ์˜ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์— ์žˆ๋Š” ๋ทฐ์— ์ „ํŒŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ๋ทฐ๋Š” @EnvironmentObject ๋ฅผ ์„ ์–ธํ•˜์—ฌ WorkoutManager ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถœ์ฒ˜:

Build a workout app for Apple Watch - WWDC21 - Videos - Apple Developer


๐Ÿ‘‰ ์• ํ”Œ ์›Œ์น˜ ๊ธฐ๊ธฐ๋กœ Xcode ์—์„œ ๋นŒ๋“œ ํ•  ๋•Œ

Apple Developer Documentation - Testing custom notification interfaces

์œ„๋Š” Notification interfaces ๋ฅผ ๊ธฐ๊ธฐ์—์„œ ๋นŒ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ด์ค€ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์šฐ๋ฆฌ๋Š” ์†๋ชฉ์— ์ฐฉ์šฉํ•˜์ง€ ์•Š์•„๋„ ์–ด๋–ค ์กฐ๊ฑด์œผ๋กœ ๊ธฐ๊ธฐ์—์„œ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๊ธฐ๋ฅผ ์†๋ชฉ์— ์ฐฉ์šฉํ•˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ notification interfaces ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ๋‹ค์Œ์˜ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  • ์• ํ”Œ ์›Œ์น˜์—์„œ ์†๋ชฉ ๊ฐ์ง€๋ฅผ ๋น„ํ™œ์„ฑํ™” ํ•ฉ๋‹ˆ๋‹ค. companion iPhone ์˜ watch ์•ฑ ๋˜๋Š” watch ์˜ Setting ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ต์…˜์€ Passcode > Wrist Detection ์— ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์• ํ”Œ ์›Œ์น˜๊ฐ€ ์ถฉ์ „๊ธฐ์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • iPhone ์„ ์ž ๊ธ‰๋‹ˆ๋‹ค.

watch-only app ์„ ๋งŒ๋“ค๋”๋ผ๋„ ๋‚ด iPhone ๊ธฐ๊ธฐ๋ฅผ ํ†ตํ•ด์„œ ์• ํ”Œ์›Œ์น˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋•Œ provisioning profile ์— ์• ํ”Œ ์›Œ์น˜ ๊ธฐ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ฐฝ์ด ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‹ ๋ขฐํ•˜๊ณ  ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๊ธฐ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-11-12 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 8 24 37