반응형

[2019.01.25]

원문 : https://www.raywenderlich.com/6992-healthkit-tutorial-with-swift-workouts

HealthKit Tutorial With Swift: Workouts

HealthKit 튜토리얼은 시스템의 Health 앱과 통합해서 HealthKit API를 사용해서 운동 기록하는 방법을 단계별으로 보여줍니다.

HealthKit 튜토리얼 시리즈로 돌아온것을 환영합니다!

이 시리즈의 첫번째 부분에서, HealthKit으로 작업하는 기초를 배웠습니다: 데이터 읽고 쓰기.

이 시리즈의 두번째이자 마지막으로, 좀 더 복잡한 타입의 데이터로 작업하는 방법을 배우게 될 것입니다: 운동(workouts).

이 프로젝트는 기존 HealthKit 튜토리얼에서 그만둔 부분을 선택합니다. 아직 이 프로젝트가 없는 경우, 튜토리얼의 위와 아래에 있는 자료 다운로드(Download materials) 해주세요.

HealthKit 운동(workout)을 만날 준비를 하세요! :]

시작하기(Getting Started)

일상생활(day-to-day)에서, 운동은 간단한 것입니다. 그것은 어떤 활동을 하면서 육체적인 노력을 증가시키는 일정 시간입니다.

대부분의 운동은 다음과 같은 속성을 하나 이상으로 가지고 있습니다.

  • 활동 타입(Activity type) (달리기(running), 사이클(cycling), 프란시스(Prancercising), 등등)
  • 거리(Distance)
  • 시작 시간과 끝 시간(Start and end time)
  • 지속시간(Duration)
  • 소모된 에너지(Energy burned)

HealthKit은 운동을 거의 같은 방법으로 생각합니다. 운동은 이러한 타입의 정보를 담고 있으며, 샘플의 집합을 가집니다. 주어진 운동에는 심박수 샘플, 거리 샘플과 이를 분류하는 활동 타입이 될수 있습니다.

이전 HealthKit 튜토리얼을 계속 사용합니다. 특별한 종류의 운동을 기록할 것입니다: Prancercise.

시작 프로젝트는 이미 Prancercise 운동을 추적할수 있는 뷰 컨트롤러를 포함하고 있습니다. 이를 보기 위해, Prancercise Workouts로 이동하고나서 + 버튼을 탭합니다.

이 뷰는 Prancercise 운동을 시작하는 버튼을 포함하고 있습니다. 버튼을 한번 탭합니다. 앱은 여러분의 Prancercise 세션을 기록을 시작할 것이며, 시작 시간과 지속 시간을 보여줍니다.

버튼을 두번째로 탭합니다. 현재 Prancercise 세션이 멈추는 것을 보게 될것입니다. 이 시점에, 운동을 기록하기 위해 완료(Done)을 탭하거나 새로운 운동 세션을 시작하기 위해(이전 세션은 지워집니다) New Prancercise 버튼을 탭 할 수 있습니다.

운동 저장하기(Saving Workouts)

현재로써는, 운동을 저장하기 위해 Done 탭할때 앱은 실제로 아무것도 하지 않습니다. 그것을 바꿀 것입니다.

우선, 다음과 같은 내용이 있습니다: Workout.swift를 열고 살펴보세요. PrancerciseWorkout 구조체가 보일것입니다.

struct PrancerciseWorkout {
  var start: Date
  var end: Date
  
  init(start: Date, end: Date) {
    self.start = start
    self.end = end
  }
  
  var duration: TimeInterval {
    return end.timeIntervalSince(start)
  }
  
  var totalEnergyBurned: Double {
    let prancerciseCaloriesPerHour: Double = 450
    let hours: Double = duration / 3600
    let totalCalories = prancerciseCaloriesPerHour * hours
    return totalCalories
  }
}

PrancerciseWorkout은 앱이 운동관련된 정보를 저장하기 위해 사용하는 모델입니다. 앱은 Prancercise 세션을 종료한 후에 Done 버튼을 탭할때마다 생성합니다.

각 PrancerciseWorkout은 다음을 기록합니다.

  1. 시작 시간과 종료 시간
  2. 지속시간
  3. 총 소모된 칼로리

앱은 여러분이 운동을 저장할때 HealthKit에서 이러한 값들을 제공합니다.

주의: 여기에서, 발목에 무거운 모래주머니를 차고 쿵쿵 거리는 음악반주와 함께 상당히 빠른 속도로 Prancercise를 유지하는 것을 가정하고 있습니다. 이런 이유로(Hence), 그 운동은 시간당 450 칼로리를 소모합니다.

이제 PrancerciseWorkout에 포함된 내용이 무엇인지 이해했으므로, 하나를 저장할 것입니다.

WorkoutDataStore.swift를 열고 save(prancerciseWorkout:completion:)을 보세요. HealthKit에 Prancercise 운동을 저장하기 위해 사용할 것입니다.

그 메소드에 다음에 오는 코드를 추가합니다.

let healthStore = HKHealthStore()
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .other

먼저 HealthKit에 데이터를 저장하기 위해 건강 저장소(health store)와 운동 구성(workout configuration)을 생성합니다. 다음으로, 그 메소드의 끝에 다음을 추가 합니다.

let builder = HKWorkoutBuilder(healthStore: healthStore,
                               configuration: workoutConfiguration,
                               device: .local())
    
builder.beginCollection(withStart: prancerciseWorkout.start) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
}

점차적으로 운동을 하고 운동 데이터를 수직하기 위해 HKWorkoutBuilder(iOS 12에서 추가) 클래스를 사용합니다. 다음으로, 소모된 칼로리 수를 얻기 위해 beginCollection(withStart:completion:)의 완료 핸들러의 닫힌 괄호 바로 앞에 다음을 추가 합니다.

guard let quantityType = HKQuantityType.quantityType(
  forIdentifier: .activeEnergyBurned) else {
  completion(false, nil)
  return
}
    
let unit = HKUnit.kilocalorie()
let totalEnergyBurned = prancerciseWorkout.totalEnergyBurned
let quantity = HKQuantity(unit: unit,
                          doubleValue: totalEnergyBurned)

이전 HealthKit 튜토리얼에서의 HKQuantity를 기억할 것입니다. 사용자의 키, 몸무게, 체질량지수를 읽고 쓰기위해 사용했었습니다. 여기에서, 이 샘플에서 소모된 총 칼로리를 저장하기 위한 양(quantity)를 생성합니다. 그 후에, 새로운 샘플을 만들수 있습니다. 방금 전체 추가한 코드 바로 다음에 이 코드를 추가합니다.

let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
              quantity: quantity,                            
              start: prancerciseWorkout.start,                           
              end: prancerciseWorkout.end)

HKCumulativeQuantitySeriesSample는 개별 값보다는 운동에 대한 전체 데이터를 저장하는 새로운 샘플 타입입니다. 이를 사용할 수 있으며, 예를들어, 농구 경기로 뛰었던 총 거리를 모든 개별 샘플들을 합산해서 수집합니다. 마지막으로, 그 메소드에 다음 코드를 추가해서 빌더(builder)에 샘플을 추가합니다.

//1. Add the sample to the workout builder
builder.add([sample]) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
      
  //2. Finish collection workout data and set the workout end date
  builder.endCollection(withEnd: prancerciseWorkout.end) { (success, error) in
    guard success else {
      completion(false, error)
      return
    }
        
    //3. Create the workout with the samples added
    builder.finishWorkout { (_, error) in
      let success = error == nil
      completion(success, error)
    }
  }
}

다음은 위 코드에서 무엇을 하는지 입니다:

  1. 샘플이 준비되었으면, 여러분이 만들고 있는 운동에 추가할 때입니다.
  2. 여기에서, 운동 데이터의 수집을 마치고 운동에 대한 종료 날짜를 설정합니다.
  3. 추가된 모든 샘플로 운동(workout)을 만듭니다. 완료 블럭에서 중복 호출하는 방법을 알아봅니다. 이는 보증합니다. 이는 다음 단계를 시도하기 전에 레이스 조건이 발생하지 않고 각 단계가 완료되는 것을 보장합니다. HealthKit과 비교해서 앱에서 올바른 데이터가 보여지지 않는 경우에, 운동(workout) 빌더(builder)의 블록 기반, 비동기 방식 때문일 수도 있습니다.

아주 적절하게, Prancercise 운동은 Other로만 분류되는 활동이지만, 원하는 경우 지원되는 활동타입을 고를수 있습니다. :]

운동이 기록된 기기가 무엇인지, (HKWorkoutBuilder를 생성할때) HealthKit에 알릴수 있다는 것을 눈치챘을지도 모릅니다. 이는 나중에 데이터를 조회할때 유용할 수 있습니다.

운동 조회하기(Querying Workouts)

이제 운동을 저장할 수 있지만, HealthKit으로 부터 운동을 불러오는 방법이 필요합니다. 이를 위해서 WorkoutDataStore에 새로운 메소드를 추가할 것입니다.

WorkoutDataSource.swift에서 save(prancerciseWorkout:completion:) 뒤에 다음 메소드를 붙여 넣습니다.

class func loadPrancerciseWorkouts(completion:
  @escaping ([HKWorkout]?, Error?) -> Void) {
  //1. Get all workouts with the "Other" activity type.
  let workoutPredicate = HKQuery.predicateForWorkouts(with: .other)
  
  //2. Get all workouts that only came from this app.
  let sourcePredicate = HKQuery.predicateForObjects(from: .default())
  
  //3. Combine the predicates into a single predicate.
  let compound = NSCompoundPredicate(andPredicateWithSubpredicates:
    [workoutPredicate, sourcePredicate])
  
  let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate,
                                        ascending: true)
}

이전 HealthKit 튜토리얼에 의하면, 이 코드의 대부분이 익숙할 것입니다. 찾고자 하는 HealthKit 데이터의 타입이 무엇인지 predicates를 결정하고, 반환한 샘플들을 어떻게 정렬할 것인지 HealthKit에게 알리는 sort descriptor를 결정합니다. 다음은 위 코드에서 무엇을 하는지 입니다.

  1. HKQuery.predicateForWorkouts(with:)는 특정 활동 타입의 운동에 대한 predicate를 지정하는 특별한 메소드 입니다. 이 경우에, 활동 타입이 other(모든 Prancercise 운동은 other 활동 타입을 사용)인 다른 모든 운동 타입을 로딩합니다.
  2. HKSource는 HealthKit에 운동 데이터를 제공한 앱을 의미합니다. HKSource.default()를 호출할때, 이 앱이라고 말합니다. sourcePredicate는 소스에서 모든 운동을 가져오며, 추측한 것처럼 이 앱입니다.
  3. Core Data 경험이 있다면 NSCompoundPredicate와 매우 비슷할수도 있습니다. 하나 이상의 필터를 결합하는 방법을 제공합니다. 마지막 결과는 소스 앱으로 활동 타입이 other이고 Procercise Tracker인 모든 운동을 가져오는 쿼리입니다.

이제 predicate를 가지고 있으며, 조회를 시작할 때입니다. 그 메소드의 끝에 다음 코드르 추가합니다.

let query = HKSampleQuery(
  sampleType: .workoutType(),
  predicate: compound,
  limit: 0,
  sortDescriptors: [sortDescriptor]) { (query, samples, error) in
    DispatchQueue.main.async {
      guard 
        let samples = samples as? [HKWorkout],
        error == nil 
        else {
          completion(nil, error)
          return
      }
      
      completion(samples, nil)
    }
  }

HKHealthStore().execute(query)

완료 핸들러에서, HKWorkout 객체의 배열로 샘플을 가져옵니다. HKSampleQuery가 기본적으로 HKSample의 배열을 반환하기 때문에, 시작시간, 종료시간, 기간, 소모된 에너지 처럼, 모든 유용한 프로퍼티를 얻기 위해, HKWorkout으로 형변환을 해야 합니다.

사용자 인터페이스로 운동 로딩하기(Loading Workouts Into the User Interface)

HealthKit으로부터 운동(workouts)을 로딩하는 메소드를 작성했었습니다. 이제는 이러한 운동들을 가지고 테이블 뷰에 채우기 위해 사용할 때입니다. 일부 설정은 이미 완료되었습니다.

WorkoutsTableViewController.swift를 열고 살펴봅니다. 몇 가지를 보게 될 것입니다.

  1. 운동을 저장한 옵셔널 배열 workouts입니다. 이것들은 이전 장의 loadPrancerciseWorkouts(completion:)을 사용해서 로딩할 것입니다.
  2. 이것은 reloadWorkouts()이름의 메소드 입니다. 화면에 뷰가 나타날때마다 viewWillApperar(_:)에서 호출합니다. 이 화면으로 이동할때마다 운동(workouts)을 새로고침(refresh) 합니다.

사용자 인터페이스를 데이터로 채우기 위해, 운동(workouts)을 로딩하고 테이블 뷰의 dataSource와 연결할 것입니다.

reloadWorkouts() 에 다음 코드를 붙여 넣습니다.

WorkoutDataStore.loadPrancerciseWorkouts { (workouts, error) in
  self.workouts = workouts
  self.tableView.reloadData()
}

여기에서, WorkoutDataStore로 부터 운동(workouts)을 로딩합니다. 그리고나서, 완료 핸들러 안에서, 지역 workouts 프로퍼티에 운동(workouts)을 할당하고 새로운 데이터로 테이블 뷰를 다시로딩합니다.

이 시점에, 테이블뷰로 운동(workouts) 데이터를 가져오는 방법이 없는 것을 알아차릴수도 있습니다. 이를 해결하기 위해, 테이블뷰의 dataSource가 있는 곳에 넣을 것입니다.

그 파일의 아래쪽 닫힌 중괄호 바로 앞에 다음 코드를 붙여넣습니다.

// MARK: - UITableViewDataSource
extension WorkoutsTableViewController {
  override func tableView(_ tableView: UITableView,
                          numberOfRowsInSection section: Int) -> Int {
    return workouts?.count ?? 0
  }
}

이는 HealthKit으로부터 로딩한 운동(workouts)의 갯수와 행(row)의 수가 일치하길 원하는 것을 말합니다. 또한, HealthKit으로 부터 아무런 운동이 로딩되지 않은 경우, 행(row)도 없고 테이블 뷰튼 비어 보일 것입니다.

주의: 앞쪽에 override 키워드가 없는 메소드를 사용하는 것이 익숙할 수 있습니다. 여기에서 override를 사용해야 하는 이유는 WorkoutsTableViewcontroller이 UITableViewController의 하위클래스(subclass)이기 때문 입니다.

UITableViewController은 UITableViewDataSource와 관련된 모든 함수들이 이미 구현되어 있습니다. 사용자정의 동작을 위해서, 이러한 기본 구현된 것들을 재정의(override) 해야합니다.

이제, 무엇을 보여줄지 테이블뷰 셀에 알려줄 것입니다. 확장의 닫는 중괄호 바로 앞에 이 메소드를 붙여 넣습니다.

override func tableView(_ tableView: UITableView,
                cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let workouts = workouts else {
    fatalError("""
      CellForRowAtIndexPath should \
      not get called if there are no workouts
      """)
  }
    
  //1. Get a cell to display the workout in
  let cell = tableView.dequeueReusableCell(withIdentifier:
    prancerciseWorkoutCellID, for: indexPath)
    
  //2. Get the workout corresponding to this row
  let workout = workouts[indexPath.row]
    
  //3. Show the workout's start date in the label
  cell.textLabel?.text = dateFormatter.string(from: workout.startDate)
    
  //4. Show the Calorie burn in the lower label
  if let caloriesBurned =
      workout.totalEnergyBurned?.doubleValue(for: .kilocalorie()) {
    let formattedCalories = String(format: "CaloriesBurned: %.2f",
                                   caloriesBurned)
      
    cell.detailTextLabel?.text = formattedCalories
  } else {
    cell.detailTextLabel?.text = nil
  }
    
  return cell
}

좋습니다! 여기에서 마법같은 일이 일어납니다.

  1. 테이블 뷰로부터 셀을 꺼내옵니다(dequeue).
  2. 운동(workouts)와 일치하는 행(row)을 얻어옵니다.
  3. 운동의 시작 날짜로 메인 라벨을 채워넣습니다.
  4. 운동이 totalEnergyBurned프로퍼티를 설정하는 경우, kilocalories 변환을 사용해서 double로 형변환 합니다. 그리고나서, 문자열 형식을 지정하고 셀의 상세 라벨에 보여줍니다.

대부분이 이전 HealthKit 튜토리얼과 매우 비슷합니다. 새로운거 하나는 소모된 칼로리에 대한 단위 변환입니다.

빌드하고 앱을 실행합니다. Prancercise Workouts로 가서 + 버튼을 탭하고, Prancercise 운동을 짧게 기록하고, Done를 탭하고 테이블뷰를 봅니다.

주의: 운동을 저장하는 동안 오류가 생기는 경우, 앱의 메인 화면에서 Authorize HealthKit을 탭하고 인증과정이 완료되었는지를 확인 합니다.

짧은 운동이지만, 소모할수 있습니다. 새로운 운동 루틴은 CrossFit에 돈을 벌수 있는 기회를 줍니다.

운동에 샘플 추가하기(Adding Samples to Workouts)

여기까지, Prancercise 운동이 단일 운동 세션으로 구성된것으로 가정했습니다. 하지만 Prancercise 가 너무 강한 경우, 약간 짧은 간격으로 분리하길 원할지도 모릅니다.

samples은, 동일한 운동을 기반으로 많은 운동 간격을 기록할 수 있습니다. 그것은 HealthKit에 운동하는 동안에 무엇을 했는지 좀 더 자세하게 보여주는 방법입니다.

모든 종류의 샘플을 운동에 추가할 수 있습니다. 원하는 경우, 거리, 소모된 칼로리, 심박수 등등을 추가할 수 있습니다.

Prancercise는 댄스에 가깝기 때문에, HealthKit 튜토리얼은 calorie burn 샘플에 중점을 둘 것입니다.

모델을 업데이트하기(Making Model Updates)

이는 Prancercise 운동에 대한 새로운 생각이고, 모델을 바꿀때가 되었다는 것을 의미합니다.

단일 Prancercise 운동 모델을 사용하는 대신에, 짧은 세션에 해당하는 운동 간격(workout interval)의 개념이 필요합니다. 이 방법은, 단일 PrancerciseWorkout은 운동하는 동안 시작과 멈춘것을 저장하는 운동 간격으로 감싸거나(wrapper)나 저장용기가 됩니다.

Workout.swift를 엽니다. PrancerciseWorkout을 PrancerciseWorkoutInterval로 다음과 같이 이름을 변경합니다.

struct PrancerciseWorkoutInterval {
  var start: Date
  var end: Date
  
  var duration: TimeInterval {
    return end.timeIntervalSince(start)
  }
  
  var totalEnergyBurned: Double {
    let prancerciseCaloriesPerHour: Double = 450
    let hours: Double = duration / 3600
    let totalCalories = prancerciseCaloriesPerHour * hours
    return totalCalories
  }
}

바뀐게 아무것도 없다는 것을 알수 있습니다. 한때 전체 운동이 이제는 운동의 일부가 되었습니다.

PrancerciseWorkoutInterval 구조체 선언 바로 아래에(beneath) 다음 코드를 붙여 넣습니다.

struct PrancerciseWorkout {
  var start: Date
  var end: Date
  var intervals: [PrancerciseWorkoutInterval]
  
  init(with intervals: [PrancerciseWorkoutInterval]) {
    self.start = intervals.first!.start
    self.end = intervals.last!.end
    self.intervals = intervals
  }
}

이제, 전체 PrancerciseWorkout은 PrancerciseWorkoutInterval 값의 배열로 구성됩니다. 배열의 첫번째 항목이 시작될때 운동은 시작되고, 배열의 마지막에 있는 항목이 종료될때 종료합니다.

이는 운동을 시간 간격으로 구성된 것으로 표현하는 좋은 방법이지만, 지속시간과 소모된 전체 에너지 개념을 아직 놓치고 있습니다. 이러한 것들이 정의되기 전까지 코드는 컴파일되지 않습니다.

함수형 프로그래밍(Functional programming)을 여기에서 사용합니다. PrancerciseWorkoutInterval에서 지속기간과 소모된 전체 에너지 프로퍼티를 합하기 위해 reduce를 사용할 수 있습니다.

PrancerciseWorkout에 있는 init(with:) 아래에 다음에 오는 계산 프로퍼티들을 붙여 넣습니다.

var totalEnergyBurned: Double {
  return intervals.reduce(0) { (result, interval) in
    result + interval.totalEnergyBurned
  }
}

var duration: TimeInterval {
  return intervals.reduce(0) { (result, interval) in
    result + interval.duration
  }
}

reduce는 시작값을 가지고(이 경우에는 0) 클로져는 이전 계산의 결과를 가져옵니다. 배열에서 각 항목에 대해서 수행합니다.

소모된 전체 에너지를 계산하기 위해서, reduce는 0에서 시작해서 0과 배열에 있는 첫번째 소모된 에너지 값을 더합니다. 그리고나서 result를 가지고 배열에 있는 다음 값과 더합니다. 일단 배열의 끝에 오면, 운동으로 인해 소모된 전체 에너지의 sum total을 가져옵니다.

Reduce는 배열을 더하기 위한 편리한 함수 입니다. 함수형 프로그램과 그 놀라움에 대해서 더 자세히 알고자 하는 경우, 이 글을 확인하세요.

운동 세션(Workout Sessions)

모델을 거의 업그레이드 했습니다. WorkoutSession.swift를 열고 살펴봅니다.

WorkoutSession은 현재 추적중인 PrancerciseWorkout 관련된 데이터를 저장하는데 사용됩니다. PrancerciseWorkout에 운동 간격의 개념을 추가했기에,WorkoutSession`은 새로운 간격을 추가해야 합니다.

WorkoutSession 클래스 안쪽에서, 상태 변수를 선언하는 곳으로 이동합니다. 다음과 같이 보입니다.

var state: WorkoutSessionState = .notStarted

그 아래에, PrancerciseWorkoutInterval 값의 배열을 선언하는 줄을 새로 추가합니다.

var intervals: [PrancerciseWorkoutInterval] = []

Prancercise 세션이 완료될때, 이 배열에 새로운 간격이 추가될것입니다. 이를 위해 함수를 추가할 것입니다.

WorkoutSession에 있는 clear() 아래에 다음에 오는 메소드를 추가합니다.

private func addNewInterval() {
  let interval = PrancerciseWorkoutInterval(start: startDate,
                                            end: endDate)
  intervals.append(interval)
}

이 메소드는 운동 세션으로부터 시작날짜와 종료날짜를 가져오고, PrancerciseWorkoutInterval을 생성합니다. 시작날짜와 종료날짜는 Prancercise 세션이 시작하고 종료할때마다 재설정 됩니다.

이제 운동 간격을 추가하기 위한 방법이 생겼으며, WorkoutSession에 있는 end() 메소드를 다음으로 교체합니다.

func end() {
  endDate = Date()
  addNewInterval()
  state = .finished
}

세션에 대한 종료날짜를 설정한 직후에 addNewInterval()을 호출하는 것을 볼수 있습니다.

또한 운동 세션을 정리하는것이 필요할때마다 배열을 정리하는 것이 중요합니다. 그래서 clear()의 뒤에 다음을 추가합니다.

intervals.removeAll()

removeAll()은 말그대로 합니다. 모든것을 제거합니다. :]

수정하는게 하나 더 있습니다. completeWorkout은 새로운 PranceciseWorkout 객체를 만들기 위해 간격을 사용해야 합니다.

completeWorkout의 선언을 다음으로 교체합니다.

var completeWorkout: PrancerciseWorkout? {
  guard state == .finished, intervals.count > 0 else {
    return nil
  }
  
  return PrancerciseWorkout(with: intervals)
}

바로 그것입니다. 이 프로퍼티는 옵셔널이며, WorkoutSession이 완료되고 최소 하나의 간격으로 기록할때 전체 PrancerciseWorkout을 반환하면 됩니다.

운동이 멈출때마다, 현재 시작날자와 정지날짜가 있는 새로운 PrancerciseWorkoutInterval이 목록에 추가됩니다. 운동을 저장하기 위해 사용자가 Done를 한번 탭해서, 이 코드는 많은 세션이 기록된 간격을 사용하여 전체(full-blown) PrancerciseWorkout 엔티티(entity)를 생성합니다.

CreateWorkoutViewcontroller에 있는 코드를 변경할 필요가 없습니다. 그 버튼 동작은 start(), end(), clear() 메소드와 같은 호출입니다. 유일한 차이점은 단일 시간 간격으로 동작하는 대신, WorkoutSession은 많은 것을 생성하고 저장합니다.

빌드하고 실행합니다. Prancercise 운동을 시작하고 정지하고나서, 목록에 운동을 추가하기 위해서 Done을 탭합니다.

주의해야 한가지는, 앱은 HealthKit에 정확한 Prancercise 운동을 기록하며, 샘플이 첨부되지는 않았습니다. PrancerciseWorkoutInterval`의 샘플로 변경하는 방법이 필요합니다.

운동을 저장하는동안 샘플 추가하기(Adding Samples While Saving a Workout)

WorkoutDataStore.swift를 열고 save(prancerciseWorkout:completion:) 바로 뒤에 새로운 메소드를 붙여넣습니다.

private class func samples(for workout: PrancerciseWorkout) -> [HKSample] {
  //1. Verify that the energy quantity type is still available to HealthKit.
  guard let energyQuantityType = HKSampleType.quantityType(
    forIdentifier: .activeEnergyBurned) else {
        fatalError("*** Energy Burned Type Not Available ***")
  }
  
  //2. Create a sample for each PrancerciseWorkoutInterval
  let samples: [HKSample] = workout.intervals.map { interval in
    let calorieQuantity = HKQuantity(unit: .kilocalorie(),
                                     doubleValue: interval.totalEnergyBurned)
    
    return HKCumulativeQuantitySeriesSample(type: energyQuantityType,
                                                  quantity: calorieQuantity,
                                                  start: interval.start,
                                                  end: interval.end)
  }
  
  return samples
}

저기요. 전에 이걸 본적이 있습니다! 이전 HealthKit 튜토리얼에서 체질량지수 샘플을 제출할때와 동일합니다. map의 안쪽에서 처리하며, PrancerciseWorkout과 관련된 각각의 PrancerciseWorkoutInterval 샘플을 만듭니다.

이제, 운동에 샘플을 첨부하기 위해 코드를 약간 수정할 것입니다.

save(prancerciseWorkout:completion:) 메소드 안쪽을 다음에 오는 코드로 교체합니다.

guard let quantityType = HKQuantityType.quantityType(forIdentifier:
  .activeEnergyBurned) else {
    completion(false, nil)
    return
}

let unit = HKUnit.kilocalorie()
let totalEnergyBurned = prancerciseWorkout.totalEnergyBurned
let quantity = HKQuantity(unit: unit,
                          doubleValue: totalEnergyBurned)
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
                                              quantity: quantity,
                                              start: prancerciseWorkout.start,
                                              end: prancerciseWorkout.end)

//1. Add the sample to the workout builder
builder.add([sample]) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
  
  //2. Finish collection workout data and set the workout end date
  builder.endCollection(withEnd: prancerciseWorkout.end) { (success, error) in
    guard success else {
      completion(false, error)
      return
    }
    
    //3. Create the workout with the samples added
    builder.finishWorkout { (_, error) in
      let success = error == nil
      completion(success, error)
    }
  }
}

다음으로 교체합니다.

let samples = self.samples(for: prancerciseWorkout)
    
builder.add(samples) { (success, error) in
  guard success else {
    completion(false, error)
    return
  }
      
  builder.endCollection(withEnd: prancerciseWorkout.end) { (success, error) in
    guard success else {
      completion(false, error)
      return
    }
        
    builder.finishWorkout { (workout, error) in
      let success = error == nil
      completion(success, error)
    }
  }
}

이코드는 Prancercise 운동을 사용해서 샘플 목록을 준비합니다. 그리고나서, 전에 완료했던 것처럼 운동 빌터(builder)에 더해줍니다.

빌드하고 앱을 실행합니다. Prancercise Workouts를 탭합니다. 그리고 나서 새로운 Prancercise 운동을 기록하기 위해 + 버튼을 탭합니다. 약간의 Prancercise 세션을 기록하고 HealthKit에 단일 Prancercise 운동을 저장하기 위해 Done를 탭합니다.

Health 앱에서 운동 샘플 보여주기(Viewing Workout Samples in the Health App)

Prancercise Tracker의 사용자 인터페이스에서는 새로운 것을 볼수 없지만, Health 앱에 읽을(peruse) 데이터가 많이 있습니다.

Health app을 엽니다. Health Data 라벨이 있는 두번째 탭을 탭하고나서, Activity를 탭합니다. 하루 동안의 운동을 자세히(breakdown)를 볼수 있습니다.

Prancercise 세션이 매우 짧게 기록되어 있는 것을 볼수 있으며, Activity는 1분동안 운동을 했다고 말합니다. 괜찮습니다. 이미 Prancercise 식이요법(regimen)의 상대적인 강도를 설정했으며, 하루 동안의 물리적인 노력(exertion)이 충분히 있어야(ought) 합니다.

Workouts를 탭합니다. 다음 화면에서는 하루 동안의 운동을 자세히 보여줍니다. 이 경우에, 모든 데이터가 어디에서 왔는지 확인하려고 합니다.

Show All Data를 탭합니다. 소스 앱과 함게, 하루 동안의 모든 운동을 보여주는 화면으로 이동할 것입니다.

RW 로고는 Prancercise Tracker로부터 가져온 운동이라는 것을 보여줍니다.

자세히 보기 위해 운동을 탭합니다. Workout Samples 섹션에서 스크롤을 하고나서 총 활성 에너지를 표시하는 셀을 탭합니다.

이 시점에, 방금 기록한 Prancercise 운동과 관련된 활성 에너지 샘플의 목록이 보여야 합니다.

샘플에서 탭하고 Prancercise 세션이 언제 시작하고 끝냈는지를 볼수 있습니다.

굉장합니다! 운동을 기록할 뿐만 아니라 운동에서 간격 훈련을 기록합니다.

여기에서 어디로 가야하나요?(Where to Go From Here?)

자료 다운로드에서 최종 프로젝트를 다운로드 받을수 있습니다.

HealthKit 튜토리얼은 HealthKit의 기본 개념과 자신의 앱에서 사용하는 방법에 대해서 제공합니다. HealthKit에 대해서 더 많은 것을 알기 위해서, 다음은 가장 관련 있는 자료입니다.

이러한 문서와 비디오를 살펴본 후에, HealthKit의 몇가지 고급 기능을 살펴보고 이 앱에 기능을 추가할 준비가 될것입니다.

예를들어, 새로운 타입의 샘플이나 운동을 추가할수 있으며, HKStaticsQuery를 사용해서 통계 계산하거나 HKObserverQuery를 사용해서 저장 정보가 변경하는지 관찰합니다.

반응형
Posted by 까칠코더
,