반응형

[최종 작성일 : 2019.01.23]

원문 : https://www.raywenderlich.com/459-healthkit-tutorial-with-swift-getting-started

HealthKit Tutorial With Swift: Getting Started

HealthKit 튜토리얼에서 HealthKit 데이터를 사용하기 위해 어떻게 권한을 요청하는지와, 게다가 HealthKit의 중앙 저장소에 데이터를 읽고 쓰는 방법을 배웁니다.

HealthKit은 iOS8에서 도입된 API입니다. 모든 건강 관련된 데이터를 중앙 저장소 역할을 하며, 사용자의 생체 프로파일을 작성하고 운동 정보를 저장합니다.

HealthKit 튜토리얼에서, 간단한 운동 추적앱을 만들고 배울 것입니다.

  • 권한 요청 방법과 HealthKit 데이터 사용하기
  • HealthKit 데이터 읽는 방법과 UITableView에 보여주기
  • HealthKit의 중앙 저장소에 데이터 쓰는 방법

HealthKit을 즐길준비가 되었나요? 계속 읽으세요!

주의 HealthKit 튜토리얼을 작업하기 위해서는 활성 iOS 개발자 계정이 필요할것입니다. 이게 없으면, HealthKit 기능을 사용하도록 설정하고 HealthKit 저장소를 사용할 수 없습니다.

시작하기(Getting Started)

샘플 앱은 최근 유명인사(celebrity)들이 홍보하는(endorsed) 운동을 하면서 소모된 칼로리를 추적합니다. 헐리우드 내부자들과 예의바른(well-mannered) 사교계 인사(socialites)들에게 Prancercise(말 걸음걸이와 유사한 걷기방법)에 대해 이야기하는 것은 분명합니다.

https://youtu.be/o-50GjySwew

시작 프로젝트 다운로드

빌드하고 앱을 실행합니다. 사용자 인터페이스의 뼈대를 보게 될 것입니다. 다음 두개의 글(articles)을 진행하는 동안 천천히 기능을 추가할 것입니다.

팀 할당하기(Assigning a Team)

HealthKit은 특별한 프레임워크입니다. 활성 개발자 계정이 없으면 앱을 사용할 수 없습니다. 개발자 계정이 있으면, 팀을 할당할 수 있습니다.

프로젝트 탐색기에서 PrancerciseTracker를 선택하고, PrancerciseTracker target를 선택합니다. General 탭을 선택하고 Team 콤보박스를 클릭합니다.

개발자 계정과 연결된(associated) 팀을 선택합니다.

식은죽 먹기 맞죠? 웁스~. 내가 말하려고 했던 것은 저칼로리(low-calorie) 감자와 야채 육수(broth)에 끓인 빨간 콩 스프였습니다.

Entitlements(사용권한)

HealthKit은 자체적인 권한 설정이 있고, 그 프레임워크를 사용해서 앱을 만들기 위해서는 사용가능하도록 설정해야 합니다.

target editor에서 Capabilites 탭을 열고, 아래 스크린샷에서 보이는 것처럼, HealthKit switch를 On 시킵니다.

Xcoder가 HealthKit을 구성할때까지 기다립니다. 여기에서는 보통 문제가 없지만, 팀을 설정하는 것과 팀 번들 식별자를 설정하는 것을 잊은 경우에는, 약간의 문제(snags)가 생길수도 있습니다.

완전히 끝났습니다. 이제 사용자에게 HealthKit을 사용할 수 있는 권한을 요청하기만 하면 됩니다.

권한(Permissions)

HealthKit은 민감한(sensitive) 개인 데이터를 처리합니다. 모든 사람이 그들의 앱이 이 정보를 사용하도록 허용하는 것이 편하지는 않습니다.

이는 HealthKit이 강력한 개인정보 보호 시스템을 가지는 이유입니다. HealthKit은 사용자가 공유하기로 동의한 데이터 종류만 사용할 수 있습니다. Prancercise Tracker 사용자의 건강 프로파일을 만들기 위해서, 먼저 각 데이터 타입을 사용(acess) 할 수 있는 권한(permission)을 요청해야 합니다.

공유 사용 설명을 업데이트하기(Updating the Share Usage Descriptions)

우선, 사용자에게 건강 정보를 요청하는 이유를 설명해야 합니다. Xcode는 앱의 Info.plist 파일에서 지정할 수 있습니다.

Info.plist를 엽니다. 그리고 나서 다음에 오는 키들을 추가합니다: 
Privacy - Health Share Usage Description Privacy - Health Update Usage Description

두개의 키에 저장된 텍스트는 HealthKit 인증 화면이 나타날때 보여집니다. Health Share Usage Description은 HealthKit의 데이터를 읽을 수 있는 부분에 해당합니다. Health Update Usage Description은 HealthKit에 기록하는 데이터에 해당합니다.

원하는 것을 무엇이든 넣을 수 있습니다. 일반적으로 다음과 같이 설명합니다, Prancercise 운동 기록을 더 잘하기 위해서, 여러분의 건강 정보를 사용할 것입니다(We will use your health information to better track Prancercise workouts).

이런한 키가 설정되지 않은 경우에, HealthKit 인증을 시도할때 앱이 크래쉬 될것이라는 것을 알고 있어야 합니다.

HealthKit 인증하기(Authorizing HealthKit)

HealthKitSetupAssistant.swift를 열고 살펴봅시다. 오류 타입과 HealthKit 인증에 사용할 메소드 본문으로 된 빈 클래스를 보게 될 것입니다.

authorizeHealthKit(completion:) 메소드는 매개변수가 없고, boolean(성공 또는 실패)과 문제가 발생했을때 옵셔널 오류를 반환하는 완료 핸들러(completion handler)를 가집니다. 이는 두가지 오류가 있을수 있습니다. 두가지 특별한 상황(circumstances)에서 그것들을 완료 핸들러에 전달합니다.

  1. HealthKit은 기기에서 사용하지 못할수도 있습니다. 이는 iPad에서 발생합니다.
  2. 일부 데이터 타입은 Healthkit의 현재 버젼에서 사용하지 못할 수도 있습니다.

이 과정은 그만합시다! HealthKit 인증을 위해서, authorizeHealthKit(completion:) 메소드는 다음과 같은 4가지 작업을 해야 합니다.

  1. 기기에서 HealthKit이 사용가능한지 확인합니다. 사용할수 없는 경우, 오류와 함께 실패로 완료하게 됩니다.
  2. Prancercise Tracker가 HealthKit에 일고 쓰기 할 건강 데이터의 타입을 준비합니다.
  3. 이러한 데이터를 읽고 쓸 타입의 목록을 구성합니다.
  4. 인증을 요청합니다. 성공하는 경우, 성공으로 완료하게 됩니다.

HealthKit 사용가능여부 확인하기(Chacking HealthKit Availability)

중요한 것부터 먼저합니다. HealthKit이 기기에서 사용가능한지 확인할 것입니다.

authorizeHealthKit(completion:) 메소드의 맨 위에 다음에 오는 코드 일부를 붙여넣습니다

//1. Check to see if HealthKit Is Available on this device
guard HKHealthStore.isHealthDataAvailable() else {
  completion(false, HealthkitSetupError.notAvailableOnDevice)
  return
}

HKHealthStore와 상호작용(interact)을 많이 하게 될것입니다. 사용자의 건강 관련 데이터를 저장하는 중앙 저장소를 나타냅니다. HKHealthStore의 isHealthDataAvailable() 메소드는 사용자의 현재 기기가 HealthKit 데이터를 지원하는지 확인하는데 도움을 줍니다.

guard 문은 기기에서 HealthKit을 사용하지 못하는 경우에 authorizeHealthKit(completion:) 메소드의 나머지 로직이 실행을 멉춥니다. 이런 일이 발생할때, 메소드는 notAvailableOnDevice 오류로 완료됩니다. 뷰 컨트롤러가 그것으로 무언가를 할수 있거나 콘솔에 로그를 출력할 수 있습니다.

데이터 타입 준비하기(Preparing Data Types)

사용자의 기기에서 HealthKit을 사용할 수 있는 것을 알게 되면, HealthKit에서 읽고 쓰기 위한 데이터의 타입을 준비해야 할때입니다.

HealthKit은 HKObjectType이라는 타입으로 작업합니다. HealthKit의 중앙 저장소로 들어오고 나가는 모든 타입은 HKObjectType의 유형입니다. 또한, HKSampleType과 HKWorkoutType을 보게 될 것입니다. 둘다 모두 HKObjecttype으로부터 상속받았고, 그것들은 기본적(basically)으로 같습니다.

첫번째 코드 부분 바로 뒤에 다음 코드를 붙여 넣습니다.

//2. Prepare the data types that will interact with HealthKit
guard   let dateOfBirth = HKObjectType.characteristicType(forIdentifier: .dateOfBirth),
        let bloodType = HKObjectType.characteristicType(forIdentifier: .bloodType),
        let biologicalSex = HKObjectType.characteristicType(forIdentifier: .biologicalSex),
        let bodyMassIndex = HKObjectType.quantityType(forIdentifier: .bodyMassIndex),
        let height = HKObjectType.quantityType(forIdentifier: .height),
        let bodyMass = HKObjectType.quantityType(forIdentifier: .bodyMass),
        let activeEnergy = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) else {
        
        completion(false, HealthkitSetupError.dataTypeNotAvailable)
        return
}

와우, 커다란 guard구문입니다! 이것은 하나의 guard사용해서 여러 옵션들을 감싸는 훌륭한 예제입니다.

주어진 생체 특성(biological characteristic)이나 수량(quantity)에 대한 HKObjectType를 만들기 위해서(In order to), HKObjectType.characteristicType(forIdentifier:) 이나 HKObjectType.quantityType(forIdentifier:) 중 하나가 필요합니다.

특성(characteristic) 타입과 수량(quantity) 타입은 모두 프레임워크에서 열거형으로 정의됩니다. HealthKit은 이것들이 로딩되어 있습니다. prancercising하는 동안에 머리가 빙빙 도는 것을 추적하는 앱에는 다양한 건강 수치(dimensions)가 있습니다.

단일 특성(characteristic)이나 샘플(sample) 타입을 사용할수 없는 경우에도, 그 메소드가 오류와 함께 완료될 것임을 알게 될 것입니다. 이는 의도적입니다. 앱은 항상 어떤 HealthKit 타입으로 작업할 것인지 정확히 알고 있어야 합니다.

읽고 쓰기 위한 데이터 타입 목록 준비하기(Preparing a list of data types to read and write)

이제 일기 타입과 쓰기 타입의 목록을 준비할 때입니다.

authorizeHealthKit(completion:)메소드의 두번째 코드 부분 바로 뒤에 다음 세번재 코드 부분을 붙여 넣습니다.

//3. Prepare a list of types you want HealthKit to read and write
let healthKitTypesToWrite: Set<HKSampleType> = [bodyMassIndex,
                                                activeEnergy,
                                                HKObjectType.workoutType()]
    
let healthKitTypesToRead: Set<HKObjectType> = [dateOfBirth,
                                               bloodType,
                                               biologicalSex,
                                               bodyMassIndex,
                                               height,
                                               bodyMass,
                                               HKObjectType.workoutType()]

HealthKit은 사용자가 쓸수 있는 데이터의 종류를 나타내는 HKSampleType의 집합(set)을 기대하고, 엡에서 읽기 위한 HKObjectType의 집합(set)을 기대합니다.

HKObjectType.workoutType()은 HKObjectType의 특별한 종류입니다. 그것은 어떤 종류의 운동을 나타냅니다.

HealthKit 인증하기(Authorizing HealthKit)

마지막 부분은 가장 쉽습니다. HealthKit에 인증을 요청이 필요할 뿐입니다. 세번째 부분 다음에 마지막 코드 부분을 붙여 넣습니다.

//4. Request Authorization
HKHealthStore().requestAuthorization(toShare: healthKitTypesToWrite,
                                     read: healthKitTypesToRead) { (success, error) in
  completion(success, error)
}

HealthKit에 인증을 요청하고 완료 핸들러를 호출합니다. HKHealthStore의 requestAuthorization(toShare: read: completion:) 메소드에서 성공과 전달된 오류 변수를 사용합니다.

그것 전용(redirect)이라고 생각할 수 있습니다. HealthKitSetupAssistant 내부의 완료를 처리하는 대신, 경고창(alert)을 보여주거나 다른 작업을 할수 있는 뷰 컨트롤러에 책임을 전달합니다.

시작 프로젝트는 이미 Authorize HealthKit 버튼을 가지고 있고, MasterViewController에 있는 authorizeHealthKit() 메소드를 호출합니다. 방금 작성한 새로운 인증 메소드를 호출하기에 최적의 장소처럼 들립니다.

MasterViewController.swift을 열고 authorizeHealthKit()으로 가서 본문에 다음 코드를 붙여넣습니다.

HealthKitSetupAssistant.authorizeHealthKit { (authorized, error) in
      
  guard authorized else {
        
    let baseMessage = "HealthKit Authorization Failed"
        
    if let error = error {
      print("\(baseMessage). Reason: \(error.localizedDescription)")
    } else {
      print(baseMessage)
    }
        
    return
  }
      
  print("HealthKit Successfully Authorized.")
}

이 코드는 방금전에 구현한 authorizeHealthKit(completion:) 메소드를 사용합니다. 인증이 완료될때, HealthKit이 성공적으로 인증되었는지 알기 위해 콘솔에 메시지를 출력합니다.

빌드하고 실행합니다. 메인 뷰에서 Authorize HealthKit을 탭하고, 인증 팝업 화면을 보게 될 것입니다.

모든 스위치를 On시키고, 화면을 스크롤해서 모두 확인하고, Allow를 클릭합니다.
Xcode의 콘솔에 다음과 같은 메시지를 보게 될 것입니다.

HealthKit Successfully Authorized.

훌륭합니다! 앱은 HealthKit의 중앙 저장소를 사용(access)할 수 있습니다. 이제 그것들을 추적할 때입니다.

특성과 샘플(Characteristics and Samples)

이번 장에서 다음을 배우게 될 것입니다:

  • 사용자의 생체 특성을 읽는 방법.
  • 다양한 타입(몸무게, 키, 등등…)의 샘플을 읽고 쓰는 방법.

신체 특성(Biological characteristics)은 혈액형(blood type)처럼, 변하지 않는 경향(tend)의 종류입니다. Samples는 몸무게(weight)처럼 자주 변하는 것들을 나타냅니다.

Prancercise 운동 요법의 효과를 제대로 추적하기 위해서, Prancercise Tracker 앱은 사용자의 몸무게와 키의 샘플을 얻어와야 합니다. 이러한 샘플들을 함께 사용해서 Body Mass Index(BMI: 체질량 지수)를 계산할 수 있습니다.

주의 BMI(Body Mass Index)는 몸이 뚱뚱한지를 나타내는데 널리 사용되고, 사람의 몸무게와 높이로 계산됩니다. 자세히 배우고 싶으면 이곳을 보세요.

특성 읽기(Reading Characteristics)

Prancercise Tracker 앱은 생체 특성을 작성하지 않습니다. HealthKit으로 부터 읽습니다. 이는 이러한 특성들을 HealthKit의 중앙 저장소에 먼저 저장되어야 하는 것을 의미합니다.

이를 이미 완료했다면, HealthKit에 여러분에 대해서 이야기할 때입니다.

기기나 시뮬레이터에서 Health App을 실행합니다: Health Data탭을 선택합니다. 그리고나서 건강 프로파일을 보기 위해 우측상단에 있는 profile icon을 탭합니다. 편집하기(Edit)를 터치하고 생년월일(Date of birth), 성별(Sex), 혈액형(Blood Type)에 대한 정보를 입력합니다.

이제 HealthKit은 여러분의 생년월일(Date of birth), 성별(Sex), 혈액형(Blood Type)을 알고 있으며, Prancercise Tracker에서 이러한 특성들을 읽을 때입니다.

ProfileDataStore에 다음 메소드를 붙여넣습니다.

class func getAgeSexAndBloodType() throws -> (age: Int, 
                                              biologicalSex: HKBiologicalSex, 
                                              bloodType: HKBloodType) {
    
  let healthKitStore = HKHealthStore()
    
  do {

    //1. This method throws an error if these data are not available.
    let birthdayComponents =  try healthKitStore.dateOfBirthComponents()
    let biologicalSex =       try healthKitStore.biologicalSex()
    let bloodType =           try healthKitStore.bloodType()
      
    //2. Use Calendar to calculate age.
    let today = Date()
    let calendar = Calendar.current
    let todayDateComponents = calendar.dateComponents([.year],
                                                        from: today)
    let thisYear = todayDateComponents.year!
    let age = thisYear - birthdayComponents.year!
     
    //3. Unwrap the wrappers to get the underlying enum values. 
    let unwrappedBiologicalSex = biologicalSex.biologicalSex
    let unwrappedBloodType = bloodType.bloodType
      
    return (age, unwrappedBiologicalSex, unwrappedBloodType)
  }
}

getAgeSexAndBloodType() 메소드는 HKHealthStore를 사용하며, 사용자의 생년월일, 성별, 혈액형을 가져옵니다. 또한 생년월일을 사용해서 사용자의 나이를 계산합니다.

  1. 이 메소드는 오류를 발생할수도 있습니다. 이는 생년월일, 성별, 혈액형이 HealthKit 중앙 저장소에 저장되어 있지 않을때 발생합니다. Health 앱에 이러한 정보를 입력했으면 오류가 발생하지 않아야 합니다.
  2. Calendar클래스를 사용해서 지정된 날짜를 Date Components로 설정해서 변환할 수 있습니다. 이는 날짜에 대한 연도를 원할때 정말로 편리(handy)합니다. 이 코드는 단순히 생년월일을 가지고, 현재 년도와의 차이를 비교합니다.
  3. unwrapped 변수들은 랩핑(wrapper) 클래스(HKBiologicalSexObject와 HKBloodTypeObject)에서 기본 열거형을 사용해야만 하는 것을 명확히 하기 위해 그렇게 이름을 지었습니다.

사용자 인터페이스 업데이트하기(Updating The User Interface)

지금 빌드하고 실행하기 원하는 경우, 아직 로직이 연결되지 않았기 때문에 UI가 아무런 변경도 되지 않습니다.

ProfileViewController.swift를 열고 loadAndDisplayAgeSexAndBloodType() 메소드를 찾습니다.

이 메소드는 생체 특성을 사용자 인터페이스에 로딩하기 위해, 사용자의 ProfileDataStore를 사용할 것입니다.

loadAndDisplayAgeSexAndBloodtype() 메소드 안에 다음 코드들을 붙여넣습니다.

do {
  let userAgeSexAndBloodType = try ProfileDataStore.getAgeSexAndBloodType()
  userHealthProfile.age = userAgeSexAndBloodType.age
  userHealthProfile.biologicalSex = userAgeSexAndBloodType.biologicalSex
  userHealthProfile.bloodType = userAgeSexAndBloodType.bloodType
  updateLabels()
} catch let error {
  self.displayAlert(for: error)
}

이 코드 부분은 튜플타입으로 나이, 성별, 혈액형을 로딩합니다. 그리고나서 UserHealthProfile 모델의 지역 인스턴스에 이러한 필드들을 설정합니다. 마지막으로, updateLabels() 메소드를 호출해서 UserHealthProfile의 새 필드로 사용자인터페이스를 업데이트 합니다.

ProfileDataStrore의 getAgeSexAndBloodType() 메소드는 오류를 발생할수 있기때문에, 이를 ProfileViewcontroller에서 처리합니다. 이 경우에, 단순히 OK버튼과 함께 오류를 가져와서 경고창에 보여줍니다.

모든게 훌륭하지만, 한가지 걸리는게 있습니다. 아직 updateLabels() 메소드에 아무것도 하지 않았습니다. 그냥 비어있는 선언입니다. 이번에는 실시간으로 사용자 인터페이스와 연결해 봅시다.

updateLabels() 메소드로 가서 본문에 다음 코드를 붙여넣습니다.

if let age = userHealthProfile.age {
  ageLabel.text = "\(age)"
}

if let biologicalSex = userHealthProfile.biologicalSex {
  biologicalSexLabel.text = biologicalSex.stringRepresentation
}

if let bloodType = userHealthProfile.bloodType {
  bloodTypeLabel.text = bloodType.stringRepresentation
}

이 코드는 매우 간단(straightforward)합니다. 사용자가 나이를 설정한 경우, 라벨에 넣습니다. 성별과 혈액형도 똑같이 처리합니다. stringRepresentation 변수는 열거형을 화면에 보여주기 위한 문자열로 변환합니다.

빌드하고 앱을 실행합니다. Profile & BMI 화면으로 이동합니다. Read HealthKit Data 버튼을 탭합니다.

Health 앱에 여러분의 정보를 입력한 경우, 이 화면에서 라벨에 표시됩니다. 그렇지 않은 경우에는 오류 메시지가 나올것입니다.

멋집니다! HealthKit으로 부터 데이터를 직접 읽고 보여주었습니다.

샘플 조회하기(Querying Samples)

이제 사용자의 몸무게와 키을 읽어올 때입니다. 이것들은 프로파일 뷰에서 BMI를 계산하고 보여주기 위해 사용됩니다.

생체 특성은 거의 변화지 않기 때문에 쉽게 사용할 수 있습니다. 샘플은 훨씬 더 정교한(sophisticated) 접근법이 필요합니다. HKQuery, 특히 HKSampleQuery를 사용합니다.

HealthKit으로 부터 샘플을 조회하기 위해 다음이 필요합니다.

  1. 조회하기(몸무게, 키, 등등) 원하는 샘플의 타입을 지정합니다.
  2. 데이터를 필터링하고 정렬하는데 도움이 되는 몇가지 추가적인 매개변수. 이를 위해 옵셔널 NSPredicate 또는 NSSortDescriptors의 배열을 사용할 수 있습니다.

주의Core Data에 대해 익숙한 경우, 아마도 몇가지 비슷한 점을 알아챘을 것입니다. HKSampleQuery는 entity type에 대한 NSFetchedRequest 와 매우 비슷하며, predicate와 sort descriptors를 지정하고나서, 객체(Object) context에 쿼리를 실행해서 결과를 가져옵니다.

쿼리를 설정하고나서, 결과를 가져오기 위해 단순히 HKHealthStore의 executeQuery() 메소드를 호출합니다.

Prancercise Tracker에서, 모든 타입의 최근 샘플을 로딩하는 단일 제네릭 함수를 생성할 것입니다. 그외 같이(That way), 몸무게와 키 모두에 사용할 수 있습니다.

ProfileDataStore.swift를 열고 getAgeSexAndBloodType 메소드 바로 아래에 다음에 오는 메소드를 붙여 넣습니다.

class func getMostRecentSample(for sampleType: HKSampleType,
                               completion: @escaping (HKQuantitySample?, Error?) -> Swift.Void) {
  
//1. Use HKQuery to load the most recent samples.  
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
                                                      end: Date(),
                                                      options: .strictEndDate)
    
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
                                      ascending: false)
    
let limit = 1
    
let sampleQuery = HKSampleQuery(sampleType: sampleType,
                                predicate: mostRecentPredicate,
                                limit: limit,
                                sortDescriptors: [sortDescriptor]) { (query, samples, error) in
    
    //2. Always dispatch to the main thread when complete.
    DispatchQueue.main.async {
        
      guard let samples = samples,
            let mostRecentSample = samples.first as? HKQuantitySample else {
                
            completion(nil, error)
            return
      }
        
      completion(mostRecentSample, nil)
    }
  }
 
HKHealthStore().execute(sampleQuery)
}

이메소드는 샘플 타입(키, 몸무게, bmi, 등등)을 가져옵니다. 그리고나서 해당 타입에 대한 최근 샘플을 조회합니다. 키(height)에 대한 샘플타입을 전달한 경우, 최근 입력받은 키를 돌려받게 될 것입니다.

여기에서 많은 일을 합니다. 몇가지를 정리해 볼것입니다.

  • HKQuery는 HealthKit 샘플 쿼리를 필터링 할수 있는 여러가지 방법을 가지고 있습니다. 그것들을 살펴볼 가치가 있습니다. 이 경우에, 기본 제공하는 날짜 창에 predicate를 사용합니다.
  • HealthKit에서 쿼리(Querying)하는 샘플은 비동기 처리(asynchronous process) 입니다. 이는 코드에서 Dispatch block의 안쪽에서 완료 핸들러를 호출하는 이유 입니다. 완료 핸들러가 메인 스레드(main thread)에서 처리하길 원하므로, 사용자 인터페이스가 응답할 수 있습니다. 그렇게하지 않은 경우, 앱은 크래쉬날 것입니다.

모두 잘 되었다면, 쿼리는 실행되고 ProfileViewController는 라벨에 내용을 넣을 수 있는 멋지고 깔끔한 샘플을 메인스레드에서 반환받게 될 것입니다. 이제 그 부분을 수행해봅시다.

사용자 인터페이스에서 샘플 보여주기(Displaying Samples in the User Interface)

이전 장에서, HealthKit에서 데이터를 로딩했었으며, ProfileViewController에 있는 모델로 저장하고나서, ProfileViewController의 updateLabels() 메소드를 사용해서 라벨에 내용을 업데이트했습니다.

이제 샘플을 로딩하고, 사용자 인터페이스를 처리하고나서 라벨을 텍스트로 채우기 위해 updateLables()를 호출하는 함수를 추가해서 이 과정을 확장하기만 하면 됩니다.

ProfileViewController.swift를 열고, loadAndDisplayMostRecentHeight() 메소드가 있는 곳에, 본문을 다음 코드를 붙여 넣습니다.

//1. Use HealthKit to create the Height Sample Type
guard let heightSampleType = HKSampleType.quantityType(forIdentifier: .height) else {
  print("Height Sample Type is no longer available in HealthKit")
  return
}
    
ProfileDataStore.getMostRecentSample(for: heightSampleType) { (sample, error) in
      
  guard let sample = sample else {
      
    if let error = error {
      self.displayAlert(for: error)
    }
        
    return
  }
      
  //2. Convert the height sample to meters, save to the profile model,
  //   and update the user interface.
  let heightInMeters = sample.quantity.doubleValue(for: HKUnit.meter())
  self.userHealthProfile.heightInMeters = heightInMeters
  self.updateLabels()
}
  1. 이 메소드는 키(Height) 샘플 타입을 생성하는 것으로 시작합니다. 그리고나서 해당 샘플 타입을 그 메소드에 전달하며, HealthKit에 최근에 기록된 키(height) 샘플이 반환될 것입니다.
  2. 샘플이 반환되고나면, 키(height)는 미터로 변환되고 UserHealthProfile 모델로 저장됩니다. 그리고 나서 라벨이 업데이트 됩니다.

주의: 일반적으로 수량 샘플을 표준 단위로 변환하려고 합니다. 이를 위해서, 위 코드는 HKQuantitySample의 doubleValue(for:) 메소드를 활용해서, 원하는 HKUnit를 전달할수 있습니다.(이 경우에는 미터)

HealthKit에서 사용가능한 몇가지 공통 클래스 메소드를 사용해서 다양한 HKUnits의 타입을 구성할 수 있습니다. 미터(meters)를 가져오기 위해, meter() 메소드를 사용하면 됩니다.

그것은 키(height)를 처리합니다. 몸무게(weight)는 어떨까요? 이는 매우 비슷하지만, ProfileViewController에 있는 loadAndDisplayMostRecentWeight() 메소드 본문을 채워야 할 것입니다.

loadAndDisplayMostRecentWeight() 메소드의 본문에 다음에 오는 코드를 붙여넣습니다.

guard let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass) else {
  print("Body Mass Sample Type is no longer available in HealthKit")
  return
}
    
ProfileDataStore.getMostRecentSample(for: weightSampleType) { (sample, error) in
      
  guard let sample = sample else {
        
    if let error = error {
      self.displayAlert(for: error)
    }
    return
  }
      
  let weightInKilograms = sample.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo))
  self.userHealthProfile.weightInKilograms = weightInKilograms
  self.updateLabels()
}

그것은 똑같은 패턴입니다. HealthKit에서 찾고자하는(retrieve) 샘플 타입을 생성하고, 모델에 저장하고 사용자 인터페이스에 업데이트 하기 위해 단위를 변환합니다.

이 시점에, 완료된것으로 생각할 수 있지만 해야할게 하나 더 있습니다. updateLabels() 함수가 새 데이터를 인식하지 못합니다. 그것을 바꿔봅시다.

updateLabels() 함수의 라벨에 혈액형을 보여주는 부분 바로 아래에, 다음을 추가합니다.

if let weight = userHealthProfile.weightInKilograms {
  let weightFormatter = MassFormatter()
  weightFormatter.isForPersonMassUse = true
  weightLabel.text = weightFormatter.string(fromKilograms: weight)
}
    
if let height = userHealthProfile.heightInMeters {
  let heightFormatter = LengthFormatter()
  heightFormatter.isForPersonHeightUse = true
  heightLabel.text = heightFormatter.string(fromMeters: height)
}
   
if let bodyMassIndex = userHealthProfile.bodyMassIndex {
  bodyMassIndexLabel.text = String(format: "%.02f", bodyMassIndex)
}

updateLabels() 함수에서 원래 패턴을 따라서, UserHealthProfile모델에서 키, 몸무게, 체질량지수(BMI)를 가져옵니다(unwrap). 이것들이 사용가능한 경우, 라벨에 적절한 문자열을 만들고 입력합니다. MassFormatter과 LengthFormatter은 수량을 문자열로 변환하는 작업을 합니다.

체질량 지수(Body Mass Index)는 실제 UserHealthProfile 모델에 저장되지 않습니다. 이는 계산을 하는 계산 프로퍼티 입니다.

bodyMassIndex 프로퍼티에서 [Command+클릭]해서, 무엇을 말하는지 볼수 있을 것입니다.

var bodyMassIndex: Double? {
    
  guard let weightInKilograms = weightInKilograms,
    let heightInMeters = heightInMeters,
    heightInMeters > 0 else {
    return nil
  }
    
  return (weightInKilograms/(heightInMeters*heightInMeters))
}

체질량 지수(BMI)는 옵셔널 프로퍼티이며, 키와 몸무게를 모두 설정하지 않으면 nil을 반환할 수 있습니다(또는 전혀 말이 안되는 숫자로 설정되어 있는 경우에도 nil을 반환). 실제 계산은 몸무게를 키 제곱으로 나눈것입니다.

주의: 앱이 읽을 데이터에 대해 HealthKit 저장소에 추가되어 있지 않은 경우에, 곧바로 막힐 것입니다. 아직 생성하지 않은 경우, 최소한 약간의 키와 몸무게 샘플을 생성해야 합니다.

Health App을 열고, Health Data탭으로 갑니다. 신체 측정(Body Measurements)옵션을 선택하고나서, 몸무게(Weight)를 고르고, 새로운 몸무게 샘플을 추가하기 위해 Add Data Point를 합니다. 키(Height)에 대해서도 이 과정을 반복합니다.

이 시점에, Prancercise Tracker는 사용자의 몸무게와 키의 최근 샘플을 읽을 수 있고, 라벨에 보여줍니다.

빌드하고 실행합니다. Profile & BMI로 이동합니다. 그리고나서 Read HealthKit Data 버튼을 탭합니다.

굉장합니다! HealthKit에 저장소에서 첫번째 샘플을 읽고 BMI를 계산하기 위해 사용됩니다.

샘플 저장하기(Saving Samples)

Prancercise Tracker은 이미 편리한 체질량지수(BMI) 계산기가 있습니다. 사용자의 BMI의 샘플을 기록해 봅시다.

ProfileDataStore.swift를 열고 다음 메소드를 추가합니다.

class func saveBodyMassIndexSample(bodyMassIndex: Double, date: Date) {
  
  //1.  Make sure the body mass type exists  
  guard let bodyMassIndexType = HKQuantityType.quantityType(forIdentifier: .bodyMassIndex) else {
    fatalError("Body Mass Index Type is no longer available in HealthKit")
  }
    
  //2.  Use the Count HKUnit to create a body mass quantity
  let bodyMassQuantity = HKQuantity(unit: HKUnit.count(),
                                    doubleValue: bodyMassIndex)
    
  let bodyMassIndexSample = HKQuantitySample(type: bodyMassIndexType,
                                             quantity: bodyMassQuantity,
                                             start: date,
                                             end: date)
    
  //3.  Save the same to HealthKit
  HKHealthStore().save(bodyMassIndexSample) { (success, error) in
      
    if let error = error {
      print("Error Saving BMI Sample: \(error.localizedDescription)")
    } else {
      print("Successfully saved BMI Sample")
    }
  }
}

약간은 익숙할 것입니다. 다른 샘플 타입으로, 먼저 HealthKit에서 샘플 타입을 사용할수 있는지 확인해야 합니다.

  1. 이 경우에, BMI에 대한 수량 타입(quantity type) 이 있는지 코드를 확인합니다. 있다면, 수량과 수량 샘플을 만들기 위해 사용됩니다. 없다면, 앱은 크래쉬납니다.
  2. HKUnit에 있는 count() 메소드는 저장하는 샘플의 타입에 대한 단위가 명확하지 않은, 특별한 경우에 사용됩니다. 미래의 어느 시점에, 체질량 지수(BMI)에 할당된 단위가 있을 수도 있지만, 지금으로는 일반적인 단위로 잘 동작합니다.
  3. HKHealthStore는 샘플을 저장하고 후행 클로져(trailing closure)에서 그 처리 과정이 성공했는지을 알려줍니다. 더 많은 것을 할수 있지만, 현재 앱은 콘솔에 출력합니다.

거의 끝났습니다. 사용자 인터페이스를 연결합니다.

ProfileViewController.swift을 열고, saveBodyMassIndexToHealthKit() 메소드를 찾습니다. 이 메소드는 사용자가 테이블뷰에 있는 BMI 저장하기(Save BMI) 버튼을 탭할때 호출됩니다.

메소드에 다음 코드를 붙여 넣습니다.

guard let bodyMassIndex = userHealthProfile.bodyMassIndex else {
  displayAlert(for: ProfileDataError.missingBodyMassIndex)
  return
}
    
ProfileDataStore.saveBodyMassIndexSample(bodyMassIndex: bodyMassIndex,
                                         date: Date())

HealthKit에서 키와 몸무게가 모두 로딩될때 반환된 값인 체질량지수(BMI)가 계산 프로퍼티인 것을 기억합니다. 이 코드는 해당 프로퍼티가 계산을 시도하고 가능한경우, savedBodyMassIndexSample(bodyMassIndex: date:) 메소드로 전달되서 쓰여집니다.

또한, 체질양지수(BMI)가 여러가지 이유로 계산될수 없는 경우에 경고 메시지를 보여줍니다.

마지막으로 Prancercise Tracker를 빌드하고 실행합니다. Profile & BMI 화면으로 이동합니다. HealthKit에서 데이터를 로딩하고나서 Save BMI 버튼을 탭합니다.

콘솔을 보세요. 다음과 같이 보이나요?

Successfully saved BMI Sample

만약 그렇다면, 축하합니다! 여러분의 BMI 샘플은 이제 HealthKit의 중앙 저장소에 저장 되었습니다. 그것을 찾을수 있는지 봅시다.

Health app을 열고, Health Data탭을 탭하고, 테이블 뷰에 있는 Body Measurements를 탭하고나서, Body Mass Index를 탭합니다.

정기적으로 체질량지수를 기록하지 않는한 다음과 같이 단일 데이터 포인트가 표시됩니다.

좋습니다. 여러분이 만든 앱에서 샘플을 볼수 있으며, 애플의 Health 앱에서도 가능합니다. 이 가능성을 상상해보세요.

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

다음은 지금까지 수정한 사항이 모두 포함되어 있는 예제 앱입니다.

축하합니다. 여러분은 HealthKit에 대한 실제 경험이 있습니다! 이제 권한을 요청하고, 생체 특성을 일고, 샘플을 읽고 쓰는 방법을 알고 있습니다.

더 많은 걸을 배우길 원하는 경우, HealthKit 튜토리얼 시리즈의 다음 파트에서 보다 더 복잡한 데이터 타입(운동: workouts)을 자세히 배우게 될 것입니다.

반응형
Posted by 까칠코더
,