[SwiftUI] HealthKit으로 현재 소모 칼로리 데이터 확인하기
[SwiftUI] HealthKit으로 칼로리 데이터 확인하기
MC3 프로젝트를 진행하고 있는데, HealthKit을 사용해서 칼로리 데이터를 가져와야 했다. 어떤 특정한 버튼을 누를 때마다 현재 소모한 칼로리를 가져오고 싶었다. 그래서 아래와 작업을 진행해 보았다.
Signing & Capability 설정
All 옆에 있는 +Capability를 눌러서 HealthKit을 추가해 주어야 한다.
HealthKit을 찾아서 추가해준다.
추가해야 권한 설정 화면이 나올 수 있다.
Info.plist 설정
마찬가지로 Target의 Info 부분에서 Privacy - Health Share Usage Description을 넣어 주어야 한다. Privacy - Health Update Usage Description도 설정해 주어야 한다.
전체 코드
import SwiftUI
import HealthKit
struct ContentView: View {
@StateObject private var healthManager = HealthKitManager()
var body: some View {
VStack {
Text("현재 칼로리: \\(healthManager.currentCalories, specifier: "%.2f")")
.font(.title)
.padding()
Button(action: {
healthManager.readCurrentCalories()
}, label: {
Text("칼로리 업데이트")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
})
Text("과거 칼로리")
.font(.title)
.padding()
List {
ForEach(healthManager.caloriesHistory, id: \\.self) { calories in
Text("칼로리 : \\(calories, specifier: "%.2f")")
}
}
}
.onAppear {
healthManager.requestAuthorization()
}
}
}
class HealthKitManager: ObservableObject {
let healthStore = HKHealthStore()
let read = Set([HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!])
let share = Set([HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!])
@Published var currentCalories: Double = 0.0
@Published var caloriesHistory = [Double]()
func requestAuthorization() {
self.healthStore.requestAuthorization(toShare: share, read: read) { (success, error) in
if success {
print("Authorization succeeded.")
} else {
print("Authorization failed. Error: \\(String(describing: error?.localizedDescription))")
}
}
}
func readCurrentCalories() {
guard let caloriesType = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) else {
print("칼로리 타입을 가져올 수 없습니다.")
return
}
let now = Date()
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: caloriesType, quantitySamplePredicate: predicate, options: .cumulativeSum) { [weak self] (_, result, error) in
guard let self = self, let result = result, let sum = result.sumQuantity() else {
if let error = error {
print("칼로리 가져오기 오류: \\(error.localizedDescription)")
}
return
}
let currentCalories = sum.doubleValue(for: .kilocalorie())
DispatchQueue.main.async {
self.currentCalories = currentCalories
self.caloriesHistory.append(currentCalories)
}
}
healthStore.execute(query)
}
}
코드 설명
SwiftUI와 HealthKit을 사용하여 칼로리 정보를 가져오고 업데이트하는 앱의 뷰와 로직을 구현한 것이다.
ContentView에서는 @StateObject 속성을 사용하여 HealthKitManager 인스턴스를 생성하고, 해당 매니저의 속성들을 뷰에서 사용한다. onAppear될 때, HealthKit 접근 권한을 가지고 있는지 판단하기 위해 requestAuthorization 함수를 실행한다.
HealthKitManager는 ObservableObject 프로토콜을 따르는 클래스로, HealthKit과의 상호작용을 관리한다. healthStore는 HKHealthStore 인스턴스로, HealthKit과의 데이터 교환을 위해 사용된다. read와 share 변수는 HealthKit에서 사용할 데이터 타입을 설정한다. @Published 속성 래퍼를 사용하여 currentCalories와 caloriesHistory가 변경될 때마다 뷰에 반영되도록 한다.
requestAuthorization 함수는 HealthKit에 대한 데이터 접근 권한을 요청한다. read와 share를 사용하여 읽기 및 쓰기 권한을 설정하고, 요청 결과에 따라 성공 또는 실패를 출력한다.
readCurrentCalories 함수는 HealthKit을 사용하여 현재 칼로리를 가져오고, 가져온 칼로리를 currentCalories에 할당하고 caloriesHistory에 추가한다. HKStatisticsQuery를 사용하여 통계 쿼리를 실행하고, 결과를 처리하여 현재 칼로리를 얻는다. 결과를 메인 스레드에서 처리하도록 하여 UI 업데이트가 제대로 이루어진다.
버튼을 누를 때마다 requestAuthorization와 readCurrentCalories가 호출되어 현재 칼로리를 업데이트하고, 해당 값은 뷰에 표시된다. 또한, 과거 칼로리는 caloriesHistory에 추가되어 리스트로 표시된다.
문제점
칼로리 업데이트 버튼을 앱 Foreground 상태에서 계속 누르면, 칼로리가 변화해도, 업데이트가 되지 않는다.
다만, 앱 Background 상태로 갔다가 와서 버튼을 누르면, 즉시 칼로리 변화가 반영이 된다.
왜 그런지 모르겠다. GPT는 이렇게 이야기하고 있다.
해당 코드는 HealthKit과의 상호작용을 위해 앱의 백그라운드에서 데이터 업데이트를 수행하도록 구현되어 있습니다. 기본적으로 HealthKit은 백그라운드 업데이트를 지원하고 있어, 앱이 백그라운드로 들어갈 때마다 업데이트를 수행할 수 있습니다.
현재 코드에서는 startObservingBackgroundUpdates 함수를 사용하여 백그라운드에서 칼로리 업데이트를 감지하고, readCurrentCalories 함수를 호출하여 업데이트된 칼로리 값을 가져오고 UI에 반영합니다. 이 함수는 앱이 백그라운드로 진입할 때 onAppear 블록에서 호출되도록 설정되어 있습니다.
따라서, 앱이 백그라운드로 들어가지 않으면 업데이트가 수행되지 않고 칼로리 값이 변경되지 않습니다. 만약 앱이 포그라운드에서 계속 실행되고 있는 상태에서 칼로리 값을 업데이트하고 싶다면, startObservingBackgroundUpdates 함수를 호출하는 대신에, 버튼을 누를 때마다 readCurrentCalories 함수를 직접 호출하도록 코드를 변경하면 됩니다.
그러나, 백그라운드에서 칼로리 업데이트를 지원하는 것이 더 효율적이고 정확한 결과를 보장하기 때문에, 일반적으로는 백그라운드 업데이트를 활용하는 것이 좋습니다.
원래 헬스킷 데이터 업데이트는 백그라운드에서 하는 것이 국룰이라고 한다.