HealthKit With Swift: Getting Started
HealthKit là một API đã được Apple giới thiệu từ phiên bản iOS 8. Nó hoạt động như một kho trung tâm cho tất cả các dữ liệu liên quan đến sức khoẻ, cho phép người dùng xây dựng hồ sơ sinh học và tập luyện workouts. Trong bài viết này, dựa trên việc tạo ra một ứng dụng Workout tracking đơn giản ...
HealthKit là một API đã được Apple giới thiệu từ phiên bản iOS 8. Nó hoạt động như một kho trung tâm cho tất cả các dữ liệu liên quan đến sức khoẻ, cho phép người dùng xây dựng hồ sơ sinh học và tập luyện workouts. Trong bài viết này, dựa trên việc tạo ra một ứng dụng Workout tracking đơn giản chúng ta sẽ biết được:
- Yêu cầu quyền truy cập HealthKit
- Đọc dữ liệu HealthKit
- Ghi dữ liệu vào HealthKit
Ok. Let's go Notes : Ví dụ này được viết trên Swift 4, Xcode 9 and iOS 11 Bài viết được dịch từ trang nên mình sẽ giữ nguyên ví dụ và hình ảnh của trang chính.
Hãy cùng làm một ứng dụng nhỏ theo dõi lượng calo bị đốt cháy cho người tập luyện thường xuyên. Đầu tiên bạn download sourcecode từ đây và mở nó trên XCode. Chạy thử nhé và giao diện của ứng dụng như sau. Chúng ta sẽ thao tác trên 2 phần :
- Authorize HealthKit
- Profile and BMI
1. Assigning a Team
HealthKit là một framework đặc biệt. Ứng dụng của bạn sẽ không thể sử dụng được HealthKit nếu bạn không có một tài khoản Apple developer. Sau khi có tài khoản Apple developer, bạn sẽ chỉ định team
Từ Xcode lựa chọn PrancerciseTracker trong navigator, sau đó chọn PrancerciseTracker target. Lựa chọn General và chọn Team
2. Entitlements
HealthKit cũng nằm trong một bộ các quyền lợi riêng gọi là entitlements, bạn sẽ cần phải cho phép nó được sử dụng trong ứng dụng. Đầu tiên bạn phải vào Apple develop account , thiết lập HealthKit là enable cho bunder identifier sử dụng cho ứng dụng của bạn
Sau đó, quay trở lại Xcode, mở tab Capabilities trong target editor, bạn bật HealthKit switch lên, sẽ cần chờ đợi trong giây lát đề Xcode cấu hình HealthKit
OK, tới đây thì bạn đã xong phần cấu hình để có thể sử dụng HealthKit trên ứng dụng của bạn. Chú ý gồm 2 bước đó là bận HealthKit trong Bunder identifier và Capabilities Bây giờ bạn chỉ cần yêu cầu người sử dụng cho phép sử dụng HealthKit.
3. Permissions
HealthKit đề cập đến dữ liệu nhạy cảm và riêng tư. Không phải ai cũng cảm thấy thoải mái khi cho phép các ứng dụng của họ truy cập vào thông tin này. Đó là lý do tại sao HealthKit có một hệ thống bảo mật mạnh mẽ. HealthKit chỉ có quyền truy cập vào các loại dữ liệu mà người dùng của bạn đồng ý chia sẻ với nó. Để xây dựng hồ sơ sức khoẻ cho người dùng Prancercise Tracker , bạn cần phải yêu cầu sự cho phép để truy cập vào từng loại dữ liệu đầu tiên.
3.1 Usage Descriptions
Trước tiên, bạn cần mô tả tại sao bạn yêu cầu các chỉ số sức khoẻ từ người dùng của bạn. Xcode cung cấp cho bạn một cách để chỉ định điều này trong tệp Info.plist của ứng dụng của bạn. Mở Info.plist và thêm vào keys : NSHealthUpdateUsageDescription -> Privacy – Health Share Usage Description NSHealthShareUsageDescription -> Privacy – Health Update Usage Description
Cả 2 nội dụng của keys sẽ hiển thị khi màn hình xác thực quyền sử dụng HeathKit xuất hiện. NSHealthUpdateUsageDescription : tương ứng với dữ liệu được ghi lên HealthKit NSHealthShareUsageDescription: đi theo phần dành cho dữ liệu được đọc từ HealthKit
Bạn có thể đặt bất cứ thứ gì bạn muốn ở đó. Thông thường nó là một lời giải thích là bạn dùng HealthKit vào việc gì trong ứng dụng của bạn. Ví dụ
Trường hợp bạn quên không thêm những keys này thì sao???
3.2 Authorizing HealthKit
Mở HealthKitSetupAssistant.swift, bạn sẽ tìm thấy một class rỗng với một phương thức sẽ được sử để authorize HealthKit
class func authorizeHealthKit(completion: @escaping (Bool, Error?) -> Swift.Void) { }
authorizeHealthKit(completion:) là phương thức không có thông số truyền vào. Nó có một completion handler cái mà sẽ trả về kiểu boolean (thành công hoặc lỗi) và optional error cho trường hợp lỗi.
Quá trình authorize HealthKit sẽ chia làm 4 bước : 1. Kiểm tra HealthKit có được hỗ trợ trên thiết bị của bạn không
//1. Check to see if HealthKit Is Available on this device guard HKHealthStore.isHealthDataAvailable() else { completion(false, HealthkitSetupError.notAvailableOnDevice) return }
Bạn sẽ tương tác với HKHealthStore khá nhiều. Nó đại diện cho kho trung tâm lưu trữ dữ liệu liên quan đến sức khoẻ của người dùng. Phương thức isHealthDataAvailable() của HKHealthStore giúp bạn tìm ra thiết bị hiện tại của người dùng có hỗ trợ dữ liệu HeathKit hay không.
2. Chuẩn bị các loại dữ liệu sẽ đọc và ghi lên HealthKit. Tiếp theo bạn sẽ chuẩn bị các loại dữ liệu sẽ được đọc và viết cho HealthKit. HealthKit làm việc với loại dữ liệu được định nghĩa trong HKObjectType. Bạn sẽ tìm thấy kiểu HKSampleType và HKWorkoutType. Cả 2 đều kế thừa từ HKObjectType Thêm mã tiếp theo ngay sau đoạn mã đầu tiên vào trong phương thức authorizeHealthKit(completion:)
//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 }
Chúng ta sử dụng một guard để unwrap các thuộc tính optional Để tạo một HKObjectType cho một đặc tính hoặc số lượng, bạn cần phải sử dụng HKObjectType.characteristicType (forIdentifier :) hoặc HKObjectType.quantityType (forIdentifier :)
3. Tổ chức các dữ liệu đó vào một danh sách các loại được đọc và ghi. Tiếp theo chuẩn bị một danh sách các loại để đọc và viết. Thêm mã tiếp theo ngay sau đoạn mã đầu tiên vào trong phương thức 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 sử dụng đối tượng HKSampleType cho các loại dữ liệu mà người dùng của bạn có thể ghi và HKObjectType cho các dữ liệu đọc. HKObjectType.workoutType () là một loại đặc biệt của HKObjectType. Nó đại diện cho bất kỳ loại tập luyện.
4. Yêu cầu ủy quyền (Request Authorization) Phần cuối cùng là cách đơn giản nhất. Bạn chỉ cần yêu cầu ủy quyền từ HealthKit.
//4. Request Authorization HKHealthStore().requestAuthorization(toShare: healthKitTypesToWrite, read: healthKitTypesToRead) { (success, error) in completion(success, error) }
Dòng code này sẽ gửi yêu cầu sử xác thực với HealthKit và sẽ trả về completion handler với giá trị là success or error từ HKHealthStore.requestAuthorization(toShare: read: completion:)
Tới đây bạn đã có thể gửi yêu cầu ủy quyền với HealthKit. Quay trở lại với button Authorize HealthKit trong ứng dụng của chúng ta. Mở file MasterViewController.swift và tìm tới phương thức authorizeHealthKit, thêm đoạn code sau :
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.") }
Sử dụng phương thức authorizedizeHealthKit (completion :) mà bạn vừa triển khai. Khi nó kết thúc, nó in một tin nhắn ngoài console để cho bạn biết nếu HealthKit đã được ủy quyền thành công.
Chạy lại ứng dụng, và click vào nút Authorize HealthKit, bạn sẽ thấy một authorization popup:
Bật tất cả các lựa chọn, cuộn màn hình để xem tất cả các thiết bị, và nhấp Allow. Bạn sẽ thấy một thông báo như thế này trong console của Xcode:
HealthKit Successfully Authorized.
Trong phần này, bạn sẽ học:
- Làm thế nào để đọc đặc điểm sinh học của người dùng của bạn..
- Làm thế nào để đọc và ghi các loại mẫu khác nhau (trọng lượng, chiều cao, vv)
Đặc điểm sinh học có xu hướng là những thứ không thay đổi, giống như nhóm máu của bạn. Các mẫu đại diện cho những điều thường thay đổi, như trọng lượng của bạn. Để theo dõi đúng hiệu quả của chế độ tập luyện, ứng dụng cần lấy mẫu trọng lượng và chiều cao của người dùng của bạn. Đặt lại với nhau, những mẫu này có thể được sử dụng để tính chỉ số khối cơ thể (BMI).
1. Reading Characteristics
Ứng dụng chúng ta đang viết không ghi các đặc điểm sinh học. Nó đọc chúng từ HealthKit. Điều đó có nghĩa là những đặc điểm này cần được lưu giữ trong kho trung tâm của HeathKit trước tiên. Nếu bạn chưa làm điều này, bạn cần nói với HeathKit một số chi tiết về bản thân bạn. Mở ứng dụng Health App trong thiết bị của bạn hoặc trong simulator. Chọn thẻ Health Data. Sau đó bấm vào biểu tượng profile ở góc trên bên phải để xem hồ sơ sức khoẻ của bạn. Nhấn Edit và nhập thông tin Date of Birth, Sex, Blood Type :
Ok, HealthKit đã biết được một số thông tin Date of Birth, Sex, và Blood Type của bạn. Giờ chúng ta sẽ đọc nó và hiển thị lên ứng dụng ta đang viết. Qua lại Xcode, mở ProfileDataStore.swift . Lớp DataDataStore sử dụng để truy cập vào tất cả các dữ liệu liên quan đến sức khoẻ cho người dùng của bạn. Bạn thêm phương thức sau vào trong DataDataStore :
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) } }
Phương thức getAgeSexAndBloodType() truy cập vào HKHealthStore, yêu cầu sử dụng Date of Birth, Sex, Blood Type. Nó cũng tính toán ra được tuổi của bạn dựa trên Date of Birth.
Để hiển thị những thông tin đọc được từ HealthKit lên UI. Mở ProfileViewController.swift và tìm tới phương thức loadAndDisplayAgeSexAndBloodType() . Thêm những dòng code sau để lấy ra các thông tin age, biologicalSex ,bloodType từ ProfileDataStore
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) }
Vì phương thức ProfileDataStore’s getAgeSexAndBloodType() có thể trả về lỗi nên bạn cần phải xử lý lỗi trả về nếu có với phương thức displayAlert(for: error) Phương thức updateLabels () hiện tại chưa làm gì cả. Do đó bạn cần kết nối dữ liệu với các user interface. Tìm tới phương thức updateLabels () và thêm dòng code vào trong :
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 }
Build và Run ứng dụng lại. Đi tới màn hình Profile & BMI. Nhấn vào nút Read HealthKit Data Nếu bạn đã nhập thông tin vào trong Health app thì nó sẽ hiển thị lên trên màn hình. Nếu chưa thì bạn sẽ nhận một thông báo lỗi.
Ok! Vậy là tới đây bạn đã biết cách đọc và hiển thị dữ liệu trực tiếp từ HealthKit.
2. Querying HealthKit
Tiếp theo chúng ta sẽ lấy dữ liệu weight, height để tính toán ra giá trị BMI. Sử dụng HKQuery, cụ thể hơn là HKSampleQuery để truy vấn dữ liệu mong muốn từ HealthKit. Lưu ý: Nếu bạn đã quen với Core Data, bạn có thể nhận thấy một số điểm tương đồng. Một HKSampleQuery rất giống với một NSFetchedRequest cho một entity type, nơi bạn chỉ định các predicate và descriptors sắp xếp, và sau đó yêu Object context để thực hiện các truy vấn để có được kết quả.
Mở ProfileDataStore.swift và thêm phương thức sau vào dưới phương thức 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) }
Tạo một truy vấn để lấy giá trị được lưu gần nhất theo HKSampleType. Các bước thực hiện
- HKQuery tạo ra một predicate filter theo thời gian
- HKSampleQuery tạo ra truy vấn để lấy ra tất cả các giá trị được lưu trước đó, sau đó lấy ra giá trị mới nhất first
- Quan trọng nhất bạn phải dùng HKHealthStore để execute truy vấn thông qua câu lệnh HKHealthStore().execute(sampleQuery)
Sau khi truy vấn được dữ liệu thông qua phương thức getMostRecentSample, với dữ liệu có được, chúng ta sẽ hiển thị lên màn hình. Mở ProfileViewController.swift và tìm tới phương thức loadAndDisplayMostRecentHeight(), thêm đoạn code sau :
//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() }
Đầu tiên chúng ta tạo HKSampleType cho .height , kiểm tra type này có hỗ trợ trong HealthKit không. Tiếp đó sử dụng phương thức getMostRecentSample() để truy vấn ra giá trị chiều cao gần nhất. Khi một giá trị được trả về, chiều cao được chuyển thành mét và được lưu trữ trên UserHealthProfile. Sau đó các nhãn được cập nhật trên phương thức updateLabels()
Tương tự như truy vấn chiều cao, chúng ta làm tương tự với cân nặng (weight) trong phương thức 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() }
Cuối cùng trong phương thức updateLabels(), chúng ta sẽ hiển thị giá trị chiều cao, cân nặng và tính toán giá trị bodyMassIndex. Thêm tiếp đoạn code sau :
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) }
Build và Run app để kiểm tra kết quả. Nhấn nút Read HealthKit Data.
Nếu bạn không thấy thông tin nào hiển thị có nghĩa là bạn chưa có dữ liệu trong HealthKit. Bạn sẽ cần thêm dữ liệu bằng cách : Mở Health App, và đi tới tab Health Data. Ở đó, chọn tùy chọn Body Measurements, sau đó chọn Weight và sau đó Add Data Point để thêm một mẫu trọng lượng mới. Lặp lại quá trình Height.
3. Saving HealthKit
Sau khi tính toán được giá trị BMI (bodyMassIndex), giờ chúng ta sẽ tìm hiểu cách ghi dữ liệu này vào trong HealthKit. Mở ProfileDataStore.swift và thêm phương thức saveBodyMassIndexSample()
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") } } }
- Kiểm tra HKQuantityType bạn sẽ ghi có tồn tại trong HealthKit hay không
- Phương pháp count () trên HKUnit là một trường hợp đặc biệt khi không có đơn vị rõ ràng cho loại mẫu bạn đang lưu trữ.
- HKHealthStore lưu mẫu và cho bạn biết nếu quá trình này thành công với closure.
Mở ProfileViewController.swift, tìm tới phương thức saveBodyMassIndexToHealthKit(). Phương thức này sẽ được gọi sau khi bạn nhấn vào Save BMI. Bạn thêm đoạn code sau :
guard let bodyMassIndex = userHealthProfile.bodyMassIndex else { displayAlert(for: ProfileDataError.missingBodyMassIndex) return } ProfileDataStore.saveBodyMassIndexSample(bodyMassIndex: bodyMassIndex, date: Date())
Bạn Build và Run ứng dụng. Đi tới màn hình Profile & BMI. Nạp dữ liệu của bạn từ HeathKit, sau đó nhấn nút Save BMI. Kiểm tra trong màn hình console Xcode
Successfully saved BMI Sample
Ok, vậy là bạn đã lưu giá trị BMI vào HealthKit thành công. Bạn có thể kiểm tra bằng cách Mở Health app, vào tab Health Data, chạm vào Body Measurements trên table view, sau đó chạm vào Body Mass Index.
Bạn có thể download toàn bộ sourcecode để có thể nắm bắt rõ hơn toàn bộ nội dung của bài viết. Hy vọng qua bài viết đã giúp bạn có chút kinh nghiệm để làm việc với HealthKit thông qua các bước :
- Yêu cầu quyền truy cập HealthKit
- Đọc dữ liệu HealthKit
- Ghi dữ liệu vào HealthKit Bài viết tiếp theo mình sẽ tìm hiểu sâu hơn vào HealthKit đó là Workouts Enjoy Coding!