[SwiftUI] FireStore에 HealthKit 데이터를 저장하는 방법
개인적으로 HealthStore 데이터를 처리할 때 데이터를 다른 데이터베이스에 저장할 필요가 없습니다.
다른 사람과 공유하려면 그게 필요해.
이번에는 헬스키트의 데이터를 Firestore에 저장하는 방법을 간단하게 요약해 보겠습니다.
저장된 데이터의 종류는 어제까지의 걸음수를 매일 합한 것이다.
권한 요청
기능의 확장성을 시야에 두다
HKSampleType
HKSampleType
에 대한 권한을 부여하는가HKSampleType을 얻기 위해 처리된 데이터의 종류를 정리합니다
var allHealthDataTypes: [HKSampleType] {
let typeIdentifiers: [String] = [
HKQuantityTypeIdentifier.stepCount.rawValue
]
return typeIdentifiers.compactMap {
getSampleType(for: $0)
}
}
func getSampleType(for identifier: String) -> HKSampleType? {
if let quantiryType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: identifier )) {
return quantiryType
}
if let categoryType = HKCategoryType.categoryType(forIdentifier: HKCategoryTypeIdentifier(rawValue: identifier)) {
return categoryType
}
return nil
}
이번 처리는 歩数
typeIdentifiers
보존하다.HKQuantityTypeIdentifier.stepCount.rawValue
에서 typeIdentifiers
에서 취득getSapmleType
.처리 데이터에 대한 쓰기 권한과 읽기 권한을 요청하는 함수를 준비합니다
func requestHealthDataAccessIfNeeded(dataTypes: [String]? = nil, completion: @escaping (_ success: Bool) -> Void) {
var readDataTypes = Set(allHealthDataTypes)
var shareDataTypes = Set(allHealthDataTypes)
if let dataTypeIdentifiers = dataTypes {
readDataTypes = Set(dataTypeIdentifiers.compactMap { getSampleType(for: $0) })
shareDataTypes = readDataTypes
}
requestHealthDataAccessIfNeeded(toShare: shareDataTypes, read: readDataTypes, completion: completion)
}
func requestHealthDataAccessIfNeeded(toShare shareTypes: Set<HKSampleType>?, read readTypes: Set<HKObjectType>?, completion: @escaping (_ success: Bool) -> Void) {
if !HKHealthStore.isHealthDataAvailable() {
fatalError("Health data is not available!")
}
print("Requesting HealthKit authorization...")
healthStore.requestAuthorization(toShare: shareTypes, read: readTypes) { (success, error) in
if let error = error {
print("requestAuthorization error:", error.localizedDescription)
}
if success {
print("HealthKit authorization request was successful!")
} else {
print("HealthKit authorization was not successful.")
}
completion(success)
}
}
HKSampleType
는 해당 데이터의 쓰기 권한과 읽기 권한을 요청하는 함수를 준비합니다.allDataTypes
에는 쓰기 데이터의 종류가 대입되고, toShare
에는 읽기 데이터의 종류가 대입된다.(이번에는 읽기만 했지만 향후 확장성을 고려해 쓰기 권한도 부여함)
HKCollectionQuery를 사용하여 데이터 가져오기, 저장
언제부터 언제까지의 데이터를 얻다
read
를 사용하여 데이터를 취득한 경우HKCollectionQuery
질의를 지정하고 실행합니다.예를 들어 2021년 9월 13일 오전 0시부터 2021년 9월 20일 오전 0시까지의 데이터를 얻으려면 다음과 같다.
let startDate = DateComponents(year: 2021, month: 9, day: 13, hour: 0, minute: 0, second: 0)
let endDate = DateComponents(year: 2021, month: 9, day: 20, hour: 0, minute: 0, second: 0)
let predicate = HKQuery.predicateForSamples(
withStart: calendar.date(from: startDate),
end: calendar.date(from: endDate)
)
이번의 경우 처음 이후predicate
에 저장된 데이터가 중복되지 않도록 지난번에 저장한 데이터와 차이가 있어야 한다.즉
Firestore
에 언제까지 데이터 기록으로 저장해야 하는가.이를 위해 준비한다
Firestore
.anchor 함수 저장 및 가져오기
anchor
는 마지막으로 언제까지 데이터를 입수했음을 나타내는 anchor
형 Object다.Firestore에 저장하면 다음
Date
실행 시query
를 startDate
로 받을 수 있습니다.class AnchorEntity: Codable, Identifiable {
var id: String
var createdAt: Date
init(id: String, createdAt: Date) {
self.id = id
self.createdAt = createdAt
}
}
func saveAnchor(from date: Date) -> Future<Void, Error> {
return Future<Void, Error> { promise in
let anchorId = IdFactory.create()
let anchorParams: [String: Any] = [
"id": anchorId,
"createdAt": Timestamp(date: date)
]
docRef.collection("anchor")
.document(anchorId)
.setData(anchorParams)
promise(.success(Void()))
}
}
anchor
는 소장에 문서로 순서대로 추가했다.func getAnchor(from date: Date) -> Future<Date, Error> {
return Future<Date, Error> { promise in
let docsRef =
docRef
.collection("anchor")
.order(by: "createdAt", descending: true).limit(to: 1)
docsRef.getDocuments { snapshot, error in
if let error = error {
print(error)
return
}
guard let snapshot = snapshot else {
print("Error fetching snapshot: \(error!)")
return
}
let anchor = try? snapshot.documents.first?.data(as: AnchorEntity.self)
if let anchor = anchor {
promise(.success(anchor.createdAt))
} else {
promise(.success(Calendar.current.date(byAdding: .day, value: -7, to: date)!))
}
}
}
}
anchor
소장품에서 최신을 꺼냅니다.anchor
취득할 수 있는 데이터(처음 저장할 때)가 없으면 7일 전의 날짜를 anchor
로 반환한다.HKCollectionQuery를 실행할 함수 준비
func fetchStatistics(with identifier: HKQuantityTypeIdentifier,
predicate: NSPredicate? = nil,
options: HKStatisticsOptions,
startDate: Date,
endDate: Date = Date(),
interval: DateComponents,
completion: @escaping (HKStatisticsCollection?, Error?) -> Void) {
guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else {
fatalError("*** Unable to create a step count type ***")
}
let anchorDate = createAnchorDate()
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: predicate,
options: options,
anchorDate: anchorDate,
intervalComponents: interval)
query.initialResultsHandler = { query, results, error in
completion(results, error)
}
healthStore.execute(query)
}
func createAnchorDate() -> Date {
let calender: Calendar = .current
var anchorComponents = calender.dateComponents([.day, .month, .year, .weekday], from: Date())
let offset = (7 + (anchorComponents.weekday ?? 0) - 2) % 7
anchorComponents.day! -= offset
anchorComponents.hour = 0
let anchorDate = calender.date(from: anchorComponents)!
return anchorDate
}
는 사전에 준비anchor
를 하면 집행하기 쉽다fetchStatistics
.이 경우HKStatisticsCollectionQuery
에서 제작된createAnchorDate
은 HK Statics CollectionQuery를 실행하기 위한 것으로 이전anchorDate
과는 무관하다.데이터 가져오기 및 저장 함수 준비
데이터가 FireStore에
anchor
로 저장됨class HealthQuantityEntity: Codable, Identifiable {
enum HealthQuantityType: String, Codable {
case step
}
var id: String
var startDate: Date
var endDate: Date
var data: Double
var dataType: HealthQuantityType
var unit: String
init(id: String,
startDate: Date,
endDate: Date,
data: Double,
dataType: HealthQuantityType,
unit: String) {
self.id = id
self.startDate = startDate
self.endDate = endDate
self.data = data
self.dataType = dataType
self.unit = unit
}
}
func saveSteps(startDate: Date, endDate: Date) -> Future<Void, Error> {
return Future<Void, Error> { promise in
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate)
let dateInterval = DateComponents(day: 1)
let statisticsOptions = HKStatisticsOptions.cumulativeSum
let initialResultsHundler: (HKStatisticsCollection?, Error?) -> Void = { (statisticsCollection, error) in
if let error = error {
promise(.failure(error))
return
}
statisticsCollection?.enumerateStatistics(from: startDate, to: endDate) { (statistics, stop) in
let stepsId = IdFactory.create()
var stepsParams: [String: Any] = [
"id": stepsId,
"startDate": Timestamp(date: statistics.startDate),
"endDate": Timestamp(date: statistics.endDate),
"data": 0,
"dataType": HealthQuantityEntity.HealthQuantityType.step.rawValue,
"unit": "歩"
]
let statisticsQuantity = statistics.sumQuantity()
let value = statisticsQuantity?.doubleValue(for: .count) {
stepsParams["data"] = value
docRef
.collection("HealthQuantity")
.document(stepsId)
.setData(stepsParams)
} else {
promise(.failure(Errors.invalid))
return
}
}
}
healthKitSerivice.fetchStatistics(with: HKQuantityTypeIdentifier.stepCount,
predicate: predicate,
options: statisticsOptions,
startDate: startDate,
interval: dateInterval,
completion: initialResultsHundler)
promise(.success(Void()))
}
}
HealthQuantityEntity
실행 후 되돌아오는 데이터는 fetchStatistics
내의 initialResultsHundler
를 통해 매일 합계 계산 결과를 계산한다.실행
위 함수를 호출하면FireStore에서 어제까지 반복하지 않고 저장할 수 있는 하루 합계입니다.
requestHealthDataAccessIfNeeded(dataTypes: [HKQuantityTypeIdentifier.stepCount.rawValue]) { (success) in
if success {
let now = Date()
let calender: Calendar = .current
var endDateComponents = calender.dateComponents([.day, .month, .year, .weekday], from: now)
endDateComponents.hour = 0
let endDate = calender.date(from: endDateComponents)!
self.getAnchor(from: endDate)
}
}
func getAnchor(from date: Date) {
getAnchor(from: date).sink { err in
print(err)
} receiveValue: { startDate in
if startDate != date {
self.saveSteps(startDate: startDate, endDate: date)
self.saveAnchor(from: date)
}
}.store(in: &cancellables)
}
취득 후enumerateStatistics
보존 단계는 anchor
에 따라 최신saveSteps
을 보존한다.func saveSteps(startDate: Date, endDate: Date) {
saveSteps(startDate: startDate, endDate: endDate).sink { err in
print(err)
} receiveValue: { _ in
}.store(in: &cancellables)
}
func saveAnchor(from date: Date) {
saveAnchor(from: date).sink { err in
print(err)
} receiveValue: { _ in
}.store(in: &cancellables)
}
Reference
이 문제에 관하여([SwiftUI] FireStore에 HealthKit 데이터를 저장하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/shikanoyouhei/articles/2ae4fda6680508텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)