HealthKit에 대해서 알아보고 데이터 가져오기 :)

오늘은 HealthKit에 대해서 알아보려고 한다 HealthKit을 활용해 iOS 기기 및 애플워치와 같은 웨어러블 디바이스에서 수집 된 걸음수, 호흡, 수면데이터, 심박수, 혈압 등 다양한 신체정보를 가져와 사용할 수 있는 편리한 프레임 워크이다.

 

https://developer.apple.com/documentation/healthkit/

 

HealthKit | Apple Developer Documentation

Access and share health and fitness data while maintaining the user’s privacy and control.

developer.apple.com

 

 

주요 사용할 수 있는 기능은 다음과 같다

  • 걸음 수 : 사용자의 걸음 수
  • 소모 칼로리 : 운동 중 소모한 칼로리 기록
  • 심박수 : 사용자의 실시간 심박수 데이터
  • 수면 데이터 : 수면의 질과 패턴을 분석
  • 호흡 수 : 분당 호흡 수 측정
  • 수분 섭취량 : 사용자가 하루 섭취한 수분의 양
  • 혈압 : 혈압 데이터를 저장하고 추적

다양한 기능을 모두 사용할 수 있다는 점이 런닝 앱을 구현하면서 아주 아주 편리하고 유용한 기능인 것 같다.

 

이 중 오늘은 이 HealthKit을 어떻게 적용하고 어떤 요소가 있는지 자세하게 한번 알아보자!

 

 

HKHealthStore

먼저 이 기능들을 사용하기 위해 필수적으로 가져와야하는 요소가 있는데 그건 바로 HKHealthStore이다!

 

HKHealthStore는 HealthKit의 중심 역할을 담당하고 이 클래스는 HealthKit 데이터를 읽고 쓰는 모든 작업을 관리한다. 그렇기 때문에 HealthKit을 사용하려면 우선적으로 해당 인스턴스를 생성해야 건강 데이터에 접근할 수 있다!

let healthStore = HKHealthStore()

let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
healthStore.requestAuthorization(toShare: [], read: [stepType]) { success, error in
    if success {
        // 권한이 성공적으로 요청됨
    }
}

이렇게 HKHealthStore를 선언해 인스턴스화 해둔 뒤 가져와서 사용하는데 사용된다 위에서 확인되는 또 다른 키워드가 확인되는데 그건 바로 HKQuantityType 이다.

 

HKSampleQuery

HKSampleQuery는 저장된 데이터를 검색하는데 사용된다. 이 쿼리는 특정 범위 내 건강 데이터를 조회할 수 있고 심박수, 칼로리 등 여러 데이터를 가져오는데 사용된다!

 

즉 어떤 데이터를 가져올지 선택하고 그 데이터의 범위, 필터 등을 설정해 원하는 정보의 범위를 설정해 가져올 수 있다는 점이다!

let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: 0, sortDescriptors: nil) { _, results, error in
    guard let samples = results as? [HKQuantitySample] else { return }
    // 가져온 데이터 처리
}
healthStore.execute(query)

예시 코드를 확인해보면 HKQuantityType을 활용해 어떤 데이터를 가져올지 forIdentifier에서 선택하면 된다. 현재 걸음수로 선택되었고 또한 시작 날짜와 끝나는 날짜를 선택해 원하는 범위만큼 가져오면 아주 간편하게 사용이 가능하다.

 

HKStatisticsQuery

HKStatisticsQuery는 주어진 시간 범위 내에서 데이터를 집계하는 쿼리이다. 누적 칼로리, 걸음 수 등 통계를 계산하는데 주로 사용되는 쿼리라고 생각하면 되고 단일 값 또는 여러 값을 집계할 수 있다.

let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let query = HKStatisticsQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, _ in
    if let sum = result?.sumQuantity() {
        let steps = sum.doubleValue(for: HKUnit.count())
        print("걸음 수: \\(steps)")
    }
}
healthStore.execute(query)

다음은 바로바로

 

HKStatisticsCollectionQuery

HKStatisticsCollectionQuery는 여러 기간에 대한 통계를 계산하는 쿼리이다. 예를 들어 하루 단위로 한달치 걸음 수 데이터를 가져와 사용자가 원하는 형식으로 제공할 수 있다.

 

예를 들어 1일 1주 1개월 등 반복적으로 데이터를 집계하고 히스토리컬 데이터를 처리해 시간에 따른 변화를 추적한다.

let query = HKStatisticsCollectionQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum, anchorDate: startDate, intervalComponents: interval)
query.initialResultsHandler = { _, results, error in
    results?.enumerateStatistics(from: startDate, to: endDate) { statistic, _ in
        if let sum = statistic.sumQuantity() {
            let steps = sum.doubleValue(for: HKUnit.count())
            print("걸음 수: \\(steps)")
        }
    }
}
healthStore.execute(query)

이렇게 HealthKit에서 데이터를 관리하고 추출하는데 필수적으로 사용되는 도구에 대해서 예제를 통해 알아봤다 다 각기 특징을 가지고 있어 적절하게 사용하면 아주 편리하고 쉽게 사용이 가능하다는 장점이 있으니 꼭 숙지하고 있도록 하자.

 

실제로 HealthKit를 통해 데이터 가져와 표현하기

그럼 여기까지 개념을 알아봤고 오늘 이 HealthKit을 통해 데이터를 가져와서 걸음수를 테이블 뷰에 넣는 과정에 대해서 구현한 내용을 정리 해보자

import HealthKit

private let healthStore = HKHealthStore()
let stepsSubject = BehaviorRelay<[String]>(value: [])

private func fetchStepData() {
    let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
    let now = Date()
    let startDate = Calendar.current.date(byAdding: .day, value: -10, to: now)!
    
    var interval = DateComponents()
    interval.day = 1
    
    let predicate = HKQuery.predicateForSamples(withStart: startDate, end: now, options: .strictStartDate)
    
    let query = HKStatisticsCollectionQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum, anchorDate: startDate, intervalComponents: interval)
    
    query.initialResultsHandler = { [weak self] _, results, error in
        guard let self = self, let results = results else {
            print("Error fetching steps: \\(String(describing: error))")
            return
        }
        
        var stepsData: [String] = []
        
        results.enumerateStatistics(from: startDate, to: now) { statistics, _ in
            if let sum = statistics.sumQuantity() {
                let steps = Int(sum.doubleValue(for: HKUnit.count()))
                let dateFormatter = DateFormatter()
                dateFormatter.dateFormat = "yyyy-MM-dd"
                let dateString = dateFormatter.string(from: statistics.startDate)
                stepsData.append("\\(dateString): \\(steps) 걸음")
            }
        }
        self.stepsSubject.accept(stepsData)
    }
    healthStore.execute(query)
}

위에서 설명했던 것 처럼 헬스킷을 임포트 해준 뒤 HKHealthStore 를 인스턴스화 해서 우선적으로 ViewModel을 만들어보자

권한을 요청하는 과정은 많이 다뤘으니 생략하고 데이터를 페치해오는 과정을 살펴보면

 

HKStatisticsCollectionQuery를 사용해 특정 기간의 걸음수를 가져올 수 있도록 구현했다. anchorDate, intervalComponents를 설정해 1일 단위로 데이터를 수집하고 KQuery.predicateForSamples를 활용해 10일 전의 데이터부터 오늘까지의 데이터만 가져오도록 설정했다.

 

가져온 데이터를 RxSwift로 구현한 stepsSubject에 넣어주고 해당 데이터가 정상적으로 바인딩 될 수 있도록 해주면 10일 간의 걸음 수 데이터가 정상적으로 넘어가게 된다!

import UIKit
import HealthKit
import RxSwift

class AlltimeView: UIView {
    private let disposeBag = DisposeBag()
    private let tableView = UITableView()
    private let viewModel = AlltimeViewModel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTableView()
        bindTableView()
        
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupTableView() {
        addSubview(tableView)
        tableView.register(AlltimeTableViewCell.self, forCellReuseIdentifier: AlltimeTableViewCell.identifier)
        tableView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }
    
    private func bindTableView() {
        viewModel.stepsSubject
            .observe(on: MainScheduler.instance)
            .bind(to: tableView.rx.items(cellIdentifier: AlltimeTableViewCell.identifier, cellType: AlltimeTableViewCell.self)) { index, data, cell in
                cell.textLabel?.text = data
            }
            .disposed(by: disposeBag)
    }
}

그 다음 ViewModel과 연결되어있는 뷰에 전달 받은 값을 바인드 해주면 테이블 뷰 내 해당하는 데이터들이 정상적으로 들어가게 된다 🙂

 

RxSwift를 사용해 이전 DataSourse를 사용하지 않으니 더 가독성이 뛰어난 코드 구현이 가능한 것 같아서 아주 편리하고 재밌다 !!!

 

결과물로 이렇게 내 폰에 저장된 걸음수가 이렇게 넘어올 수 있게 된다.. 아주 신기하고 재밌네..

일단 데이터를 가져오는 테스트는 성공했으니 이걸 활용해서 생체 데이터를 가져와 활용하는 방법을 고민해보자 !!

오늘은 여기까지!!