안녕하세요~~ 개발하는 윤기사입니다!!
이번 포스팅은 저번 헬스킷 1편에 이어서 2편입니다!!
[iOS_Swift] HealthKit - 헬스킷 : 건강 데이터 사용해보자! _ 36
안녕하세요!! 이번 글에서는 애플 헬스킷(HealthKit)에 대한 설명을 해드리고자 합니다! 현재 진행하고 있는 프로젝트에서, 사용자의 건강 데이터를 이용해 걸음수, 심박수, 칼로리 등 데이터를 건
swiftyun.tistory.com
위 글에서는 헬스킷에 대한 권한 허용, 그리고 어떻게 데이터를 쓰고(Write) 읽을지에(Read) 대한 내용이었다면,
이번 포스팅은 HealthKit내 데이터 타입 중 HKQuantityType에 대해 알아보고자 합니다.
더 나아가 HKSampleQuery를 이용해 query도 만들어서 건강앱에 저장되어 있는 데이터를 가져와 볼 겁니다!
추후에는 HKStatisticsQuery를 이용해 걸음수와 칼로리를 가져와보고, CategoryType를 이용해 수면 데이터도 가져와 볼 거예요!
바로 시작해 볼게요!
애플 공식 문서에 따르면, HKQuantityType은 숫자 값으로 저장되어 있는 정보에 샘플을 식별하는 타입이라고 명시되어 있어요!
예를 들면, 걸음수나 심박수, 소모 칼로리 같은 경우는 숫자 값으로 저장되어 있죠? 그래서 QuantityType을 사용한답니다!
평소 사용자가 애플워치를 착용하고 다녔다면, 건강 앱에 걸음수, 심박수와 소모 칼로리가 기록이 되어있을 거예요!
우리는 이제, 이 중에서 심박수, 소모 칼로리, 걸음수를 가져와보도록 할 겁니다!
HealthKit에서 QuantityType의 데이터만 호출할 수 있는 메서드를 하나 만들어볼게요!
메서드 내에서 사용될 3가지 Class입니다!
1. predicate : Sample 시작 시간과 마지막 시간을 설정하는 부분
2. sortDescriptor : 어떤 것을 기준으로 정렬할 지에 대한 설정 -> EndDate를 기준으로 정렬할 겁니다!
3. quantityType : Query로 만들 데이터 타입을 정의하는 부분입니다. -> 어떤 타입으로 할 건지 정하는 겁니다.
HKQuantityTypeIdentifier | Apple Developer Documentation
The identifiers that create quantity type objects.
developer.apple.com
//걸음수
static let stepCount: HKQuantityTypeIdentifier
//소모 칼로리
static let activeEnergyBurned: HKQuantityTypeIdentifier
//심박수
static let heartRate: HKQuantityTypeIdentifier
/**
- note: HealthKit QuantityType 데이터 정보 호출
- parameters:
- startDate: 시작 날짜
- endDate: 끝난 날짜
- type: 쿼리 타입 .stepCount(걸음수), .heartRate(심박수), .activeEnergyBurned(소모 칼로리)
*/
public func getQuantityTypeValues(startDate: Date, endDate: Date, type: HKQuantityTypeIdentifier, completion: @escaping([HKQuantitySample])->()) {
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)
let quantityType = HKQuantityType.quantityType(forIdentifier: type)!
let query = HKSampleQuery(sampleType: quantityType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (query, result, error) -> Void in
if error != nil { return }
var quantityList: [HKQuantitySample] = []
for (_, quantitySamples) in (result ?? []).enumerated() {
guard let data: HKQuantitySample = quantitySamples as? HKQuantitySample else { return }
quantityList.append(data)
}
completion(quantityList)
}
healthStore.execute(query)
}
이런 방식으로, SampleQuery에는 어떠한 데이터 타입인지, 어느 시점의 데이터를 불러올 건지, 개수는 몇 개를 제한으로 둘 건지, 정렬은 어떻게 할 것인지에 대한 설정을 하게 됩니다.
위 3가지 Class를 이용해서 Query를 설정해 준 후 execute 하면 query를 실행하게 됩니다!
query를 execute 하게 되면 query, result, error 세 가지의 정보를 가지고 핸들러를 통한 데이터 전달이 이루어집니다!!
필요한 데이터들의 집합은 HKQuantitySample 타입으로 똑같은 타입의 빈 배열을 하나 선언해 주고,
그 배열 안에 담아 completion으로 데이터를 전달해 주겠습니다!
자, 이제 받은 값으로 우리가 필요로 하는 타입의 데이터로 가공해 보겠습니다! 출발~
1. 걸음수
- 걸음수를 담기 위한 StepModel을 하나 만듭니다.
import Foundation
struct StepModel {
//list
var stepCnt : Int = 0
var historyDateTime : Date = Date()
var deviceTypeCode : DeviceTypeCode? = nil
public init(historyDateTime:Date, stepCnt:Int, deviceTypeCode:DeviceTypeCode){
self.stepCnt = stepCnt
self.historyDateTime = historyDateTime
self.deviceTypeCode = deviceTypeCode
}
func toDictionary() -> [String: Any] {
var dic = [
"stepCnt" : self.stepCnt.toString(),
"historyDateTime" : self.historyDateTime.yyyyMMddHHmmss,
"deviceTypeCode" : self.deviceTypeCode!.rawValue
]
return dic
}
}
- 받은 HKQuantitySample 배열을 for문을 통해 data에 접근을 합니다.
- stepCount는 data.quantity.doubleValue(for: .count()) 메서드를 이용해서 접근 할 수 있습니다!
/** 헬스킷 걸음 데이터 조회 */
public func getStepData(_ startDate: Date? = nil) {
HealthKitManager.shared().getQuantityTypeValues(startDate: startDate, endDate: Date(), type: .stepCount) { dataList in
var dicList = [[String: Any]]()
/** 헬스킷 데이터 리스트에 추가 */
for data in dataList {
let stepCnt = Int(data.quantity.doubleValue(for: .count()))
let historyDateTime = data.endDate
let deviceTypeCode = DeviceTypeCode.APP
let stepModel = StepModel(historyDateTime: historyDateTime, stepCnt: stepCnt, deviceTypeCode: deviceTypeCode).toDictionary()
dicList.append(stepModel)
}
self.completionHandler(.step, dicList)
}
}
[
"historyDateTime": "20231027170000",
"stepCnt": "1679",
"deviceTypeCode": "1402"
]
2. 소모 칼로리
- 칼로리 정보를 담기 위한 CaloriesModel을 하나 만듭니다.
import Foundation
struct CaloriesModel {
var calorie : Double? = nil
var historyDateTime : Date? = nil
var deviceTypeCode : DeviceTypeCode? = nil
public init(historyDateTime:Date, calorie:Double, deviceTypeCode:DeviceTypeCode){
self.calorie = calorie
self.historyDateTime = historyDateTime
self.deviceTypeCode = deviceTypeCode
}
func toDictionary() -> [String: Any] {
var dic = [
"calorie" : self.activityValue?.toString(),
"historyDateTime" : self.historyDateTime!.yyyyMMddHHmmss,
"deviceTypeCode" : self.deviceTypeCode!.rawValue
]
return dic
}
}
- 걸음수와 마찬가지로 받은 HKQuantitySample 배열을 for문을 통해 data에 접근을 합니다.
- 소모 칼로리는 data.quantity.doubleValue(for: .kilocalorie()) 메서드를 이용해서 접근 할 수 있습니다!
/** 헬스킷 소모 칼로리 데이터 조회 */
public func getEnergyBurnedData(_ startDate: Date? = nil) {
HealthKitManager.shared().getQuantityTypeValues(startDate: startDate, endDate: Date(), type: .activeEnergyBurned) { dataList in
var dicList = [[String:Any]]()
for data in dataList {
let carlories = self.decimalRound(data.quantity.doubleValue(for: .kilocalorie()), decimal: 3) /** 소수점 3자리까지 설정, 나머지 반올림 */
let historyDateTime = data.endDate
let deviceTypeCode = DeviceTypeCode.APP
let caloriesModel = CaloriesModel(historyDateTime: historyDateTime, calorie: carlories, deviceTypeCode: deviceTypeCode).toDictionary(bandType: .appleWatch)
dicList.append(caloriesModel)
}
self.completionHandler(.activeEnergyBurned, dicList)
}
}
/** 소수점 n자리까지 설정, 나머지 반올림 */
private func decimalRound(_ double: Double, decimal: Int) -> Double {
let digit: Double = pow(10, Double(decimal)) /** 10의 3제곱 */
return round(double * digit) / digit
}
[
"historyDateTime": "20231023142332",
"calorie": "0.039",
"deviceTypeCode": "1234"
]
3. 심박수(heartRate)
- 심박수 정보를 담기 위한 HeartRateModel을 하나 만듭니다.
import Foundation
struct HeartRateModel {
var heartbeat : Int? = nil
var historyDateTime : Date? = nil
var deviceTypeCode : DeviceTypeCode? = nil
public init(historyDateTime:Date, heartbeat:Int, deviceTypeCode: DeviceTypeCode){
self.heartbeat = heartbeat
self.historyDateTime = historyDateTime
self.deviceTypeCode = deviceTypeCode
}
func toDictionary() -> [String: Any] {
var dic = [
"heartbeat" : self.heartbeat!.toString(),
"historyDateTime" : self.historyDateTime!.yyyyMMddHHmmss,
"deviceTypeCode" : self.deviceTypeCode!.rawValue
]
return dic
}
}
- 소모 칼로리와 마찬가지로 받은 HKQuantitySample 배열을 for문을 통해 data에 접근을 합니다.
- 심박수는 data.quantity.doubleValue(for: .count().unitDivided(by: .minute())) 메서드를 이용해서 값을 가져옵니다.
- 심박수는 분당 횟수를 평균으로 하기 때문에 count()를 minute()으로 Divided 해주는 겁니다!
/** 헬스킷 심박수 데이터 조회 */
public func getHeartRateData(_ startDate: Date? = nil) {
HealthKitManager.shared().getQuantityTypeValues(startDate: startDate, endDate: Date(), type: .heartRate) { dataList in
var dicList = [[String: Any]]()
for data in dataList {
let heartRate = Int(data.quantity.doubleValue(for: .count().unitDivided(by: .minute())))
let historyDateTime = data.endDate
let deviceTypeCode = DeviceTypeCode.APP.rawValue
let heartRateModel = HeartRateModel(historyDateTime: historyDateTime, heartbeat: heartRate, deviceTypeCode: DeviceTypeCode(rawValue: deviceTypeCode) ?? .APP).toDictionary()
dicList.append(heartRateModel)
}
self.completionHandler(.heartRate, dicList)
}
}
[
"historyDateTime": "20231023142332",
"heartbeat": "84",
"deviceTypeCode": "1234"
]
이렇게 각 Model에 값을 담고, Dictionary Type으로 변환까지 완료해 봤습니다!
생각보다 간단하죠?
다음 포스팅에서는 수면 데이터를 다뤄볼 겁니다.
HKCategoryType으로 까다로운 게 많기 때문에, 더 자세하게 설명을 해보도록 할게요!
이상 iOS_HealthKit - HKQuantityType에 대한 포스팅을 마치겠습니다!
'iOS_Swift 앱개발👍' 카테고리의 다른 글
[iOS_Swift] Fastlane을 이용해 CI/CD를 구축해보자 - 1_42 (2) | 2023.11.22 |
---|---|
[iOS_Swift] HealthKit - 헬스킷 : 수면 데이터 마스터 (With HKCategoryType)_ 41 (1) | 2023.11.13 |
[iOS_Swift] QRCode Generator Framework로 만들기! _ 39 (0) | 2023.10.30 |
[iOS_Swift] QRCode Generator(CIFilter) 만들기! _ 38 (1) | 2023.10.21 |
[iOS_Swift] AVFoundation, QRCodeReader기 만들기! _ 37 (4) | 2023.10.17 |