Live heart rate record time interval - WatchOS - swift

I have created a watch app with live heart rate record, for that i have used the workout session and HKAnchoredObjectQuery to fetch the results. In this apple has mentioned that, every 5 seconds will update the heart rate value, but in my case it will take upto 5-10 sec to update. I am using watch serious 3 and i need to collect the value for exactly every 5 seconds.
func startWorkout() {
if (session != nil) {
return
}
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .walking
workoutConfiguration.locationType = .outdoor
do {
session = try HKWorkoutSession(healthStore: healthStore, configuration: workoutConfiguration)
session?.delegate = self
} catch {
print("Unable to create the workout session!")
}
session?.startActivity(with: Date())
}
func startHeartRateStreamingQuery(_ workoutStartDate: Date){
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return nil }
let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictEndDate )
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
self.updateHeartRate(sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples)
}
healthStore.execute(heartRateQuery)
}
func updateHeartRate(_ samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
for sample in heartRateSamples {
let timeStamp = sample.startDate
let value = sample.quantity
print("\(timeStamp)_\(value)")
}
}

Related

How to get Body Fat Percentage data from HealthKit?

I'm trying to create a widget for myself so I can see my measurements on the screen.
I successfully get Body Mass and Body Mass Index data from HealthKit, but my code doesn't work for fetching the Body Fat Percentage data.
Couldn't find anything about Body Fat Percentage on my search, so I wanted to ask if I need to use a different way to fetch its data?
Function for fetching the data:
class func getMostRecentSample(for sampleType: HKSampleType,
completion: #escaping (HKQuantitySample?, Error?) -> Swift.Void) {
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
end: Date(),
options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
ascending: false)
let limit = 1
// This step won't execute as usual while working for Body Fat Percentage data;
let sampleQuery = HKSampleQuery(sampleType: sampleType,
predicate: mostRecentPredicate,
limit: limit,
sortDescriptors: [sortDescriptor]) { (query, samples, error) in
DispatchQueue.main.async {
guard let samples = samples,
let mostRecentSample = samples.first as? HKQuantitySample else {
completion(nil, error)
return
}
completion(mostRecentSample, nil)
}
}
HKHealthStore().execute(sampleQuery) // then jumps to here.
}
Function for getting the string from the fetched data:
class func getSamples(for sampleType: HKSampleType,
unit: HKUnit,
completion: #escaping (String?, Error?) -> Swift.Void) {
getMostRecentSample(for: sampleType) {(sample, error) in
guard let sample = sample else {
return
}
let BMI = String(sample.quantity.doubleValue(for: unit))
// let weight = String(sample.quantity.doubleValue(for: unit))
// let fatPercentage = String(sample.quantity.doubleValue(for: unit))
completion(BMI, nil)
}
}
Sending the data to Timeline then preview it:
func getTimeline(in context: Context, completion: #escaping (Timeline<WeightEntry>) -> Void) {
let currentDate = Date()
let refreshDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!
guard let bodyMass = HKObjectType.quantityType(forIdentifier: .bodyMass),
let bodyMassIndex = HKObjectType.quantityType(forIdentifier: .bodyMassIndex),
let bodyFatPercentage = HKObjectType.quantityType(forIdentifier: .bodyFatPercentage) else {
return
}
ProfileDataStore.getSamples(for: bodyMassIndex,unit: HKUnit.count()) { (sample, error) in
guard let sample = sample else {
return
}
let entry = WeightEntry(date: currentDate, value: sample)
let timeline = Timeline(entries: [ entry ], policy: .after(refreshDate))
completion(timeline)
}
}
What unit are you passing as body fat? Should be percentage.

Update two fields at once with updateData

I am changing my online status with this code:
static func online(for uid: String, status: Bool, success: #escaping (Bool) -> Void) {
//True == Online, False == Offline
let db = Firestore.firestore()
let lastTime = Date().timeIntervalSince1970
let onlineStatus = ["onlineStatus" : status]
let lastTimeOnline = ["lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(lastTimeOnline) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
ref.updateData(onlineStatus) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
}
I update the lastTimeOnline and the onlineStatus.
I listen to this updates via:
// Get the user online offline status
func getUserOnlineStatus(completion: #escaping (Dictionary<String, Any>) -> Void) {
let db = Firestore.firestore()
db.collection("users").addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .modified) {
//GETS CALLED TWICE BUT I ONLY WANT ONCE
print("modified called..")
guard let onlineStatus = diff.document.get("onlineStatus") as? Bool else {return}
guard let userId = diff.document.get("uid") as? String else {return}
var userIsOnline = Dictionary<String, Any>()
userIsOnline[userId] = [onlineStatus, "huhu"]
completion(userIsOnline)
}
}
}
}
The problem is now, since I use ref.updateData twice, my SnapshotListener .modified returns the desired data twice.
How can I update two fields in a single call, so my .modified just return one snapshot?
You can try to combine them
let all:[String:Any] = ["onlineStatus" : status ,"lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(all) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}

AVAssetExportSession progress not updating

I make use of AVAssetExportSession to export movies to MP4 format using the codes below:
Step 1: User clicks a button to start conversion
#IBAction func clickConvert(_ sender:UIButton) {
self.convertProgress?.progress = 0
self.convertProgress?.isHidden = false
var preset = AVAssetExportPresetHighestQuality
switch self.qualitySelection?.selectedSegmentIndex {
case 0:
preset = AVAssetExportPresetLowQuality
break
case 1:
preset = AVAssetExportPresetMediumQuality
break
case 2:
preset = AVAssetExportPresetHighestQuality
break
default:
break
}
DispatchQueue.global(qos: .background).async {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMddHHmmss"
let fileName = formatter.string(from: Date()) + ".mp4"
let convertGroup = DispatchGroup()
convertGroup.enter()
do {
let documentDirectory = try self.fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let filePath = documentDirectory.appendingPathComponent(fileName)
if(self.videoURL != nil) {
self.convertVideo(fromURL: self.videoURL!, toURL: filePath, preset: preset, dispatchGroup: convertGroup)
} else {
print("nil Video URL")
}
convertGroup.notify(queue: DispatchQueue.main) {
// reset Convert button state
self.convertButton?.titleLabel?.text = "Convert"
self.convertButton?.isEnabled = true
self.delegate?.updateFileList()
// Take back to old VC, update file list
if let navController = self.navigationController {
navController.popViewController(animated: true)
}
}
} catch {
print(error)
}
}
}
Step 2: Trigger convert video function
func convertVideo(fromURL: URL, toURL: URL, preset:String, dispatchGroup: DispatchGroup) {
let outFileType = AVFileType.mp4
let inAsset = AVAsset(url: fromURL)
let startDate = Date()
AVAssetExportSession.determineCompatibility(ofExportPreset: preset, with: inAsset, outputFileType: outFileType, completionHandler: { (isCompitable) in
if !isCompitable {
return
}
guard let export = AVAssetExportSession(asset: inAsset, presetName: preset) else {
return
}
export.outputFileType = outFileType
export.outputURL = toURL
export.shouldOptimizeForNetworkUse = true
let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
let range = CMTimeRangeMake(start: start, duration: inAsset.duration)
export.timeRange = range
// Timer for progress updates
self.exportTimer = Timer()
if #available(iOS 10.0, *) {
print("start exportTimer")
self.exportTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true, block: { _ in
let progress = Float(export.progress)
print("Export Progress: \(progress)")
self.updateProgressDisplay(progress: progress)
if progress < 0.99 {
let dict:[String: Float] = ["progress": progress]
NotificationCenter.default.post(name: Notification.Name(Constants.Notifications.ConvertProgress.rawValue), object: nil, userInfo: dict)
}
})
}
export.exportAsynchronously { () -> Void in
// Handle export results
switch export.status {
case .exporting:
print("Exporting...")
self.updateProgressDisplay(progress: export.progress)
break
case .failed:
print("Error: %#!", export.error!)
break
case .cancelled:
print("export cancelled")
break
case .completed:
let endDate = Date()
let elapsed = endDate.timeIntervalSince(startDate)
print("Elapsed: \(elapsed)")
print("successful")
self.exportTimer?.invalidate() // Stop the timer
self.generateThumbnail(path: toURL)
break
default:
break
}
dispatchGroup.leave()
}
})
}
However, status update is not working, as the timer exportTimer never fires (attempt 1), and the exportSession.exporting case never fires (attempt 2).
p.s. The video can be converted without any problem
p.s. the Notification has been added in viewDidLoad() as follow:
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveConvertProgress(_:)), name: Notification.Name(Constants.Notifications.ConvertProgress.rawValue), object: nil)
Status update functions (both attempts) are as follow:
#objc func onDidReceiveConvertProgress(_ notification:Notification) {
print ("onDidReceiveConvertProgress")
if let data = notification.userInfo as? [String:Float] {
print("Progress: \(String(describing: data["progress"]))")
self.convertProgress?.progress = data["progress"]!
}
}
func updateProgressDisplay(progress: Float) {
print("updateProgressDisplay")
self.convertProgress?.progress = progress
}
What did I miss?
I'm not sure if you figured this out, but just in case someone else try your code, the problem why the progress timer is not firing is because you missed two things.
You never called the function to start the timer. e.g. self.exportTimer.fire()
You have to make sure to update this timer on the main Queue.
I had the same problems and doing these two things fixed my issue.

EventKit Unit testing - stall on main thread

I need to create multiple calendar events for Unit Test in Xcode. The call needs to be async, because of the access rights request:
eventStore.requestAccess(to: EKEntityType.event, completion: {
granted, error in
//create events
})
This is done in setUp() in a loop to create multiple events. The problem is that not all the completion blocks are done when the test starts running. I have implemented the waitForExpectations() method, to make it wait till the counter meets the requirements. But I get the following error message:
Stall on main thread
Here is the relevant code - setUp:
override func setUp()
{
super.setUp()
if let filepath = Bundle.main.path(forResource: "MasterCalendar", ofType: "csv") {
do {
let expectationCreation = expectation(description: "create events expectation")
let contents = try String(contentsOfFile: filepath)
//print(contents)
let testDataArray = importTestDataArrayFrom(file: contents)
createTestEvents(testDataArray: testDataArray)
{ (eventsArray) in
self.testEvents = eventsArray
print("finished creating events")
expectationCreation.fulfill()
}
waitForExpectations(timeout: 10.0) { (error) in
if error != nil {
XCTFail((error?.localizedDescription)!)
}
}
} catch {
print("contents could not be loaded")
}
} else {
print("example.txt not found!")
}
}
Create events:
func createTestEvents(testDataArray: [TestData], completion: #escaping (_ eventArray: [EKEvent]) -> Void) -> Void
{
let eventStore = EKEventStore()
var testEvents = [EKEvent]()
var index = 0
for testDataEvent in testDataArray
{
eventStore.requestAccess(to: EKEntityType.event, completion: {
granted, error in
if (granted) && (error == nil) {
print("===GRATNED, CREATING EVENT-", index)
index += 1
let event = EKEvent.init(eventStore: eventStore)
event.title = testDataEvent.combinedFields
event.startDate = Date()
event.endDate = Date()
event.isAllDay = false
var attendees = [EKParticipant]()
for i in 0 ..< 5 {
if let attendee = self.createParticipant(email: "test\(i)#email.com") {
attendees.append(attendee)
}
}
event.setValue(attendees, forKey: "attendees")
try! eventStore.save(event, span: .thisEvent)
let meetingObj = Parser.parse(calendarEvent: event)
testDataEvent.meeting = meetingObj
testEvents.append(event)
if(testEvents.count == testDataArray.count){
completion (testEvents)
}
}
})
}
}

Write HeartRate on HealthKit

I have a problem to send the heart rate data to healthkit.
I want to read (in apple watch display) and write the heart rate data for my apple watch app.
i don't have any problem to read, but i can't send data.
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
dispatch_async(dispatch_get_main_queue()) {
guard let sample = heartRateSamples.first else{return}
let value2 = sample.quantity.doubleValueForUnit(self.heartRateUnit)
self.label.setText(String(UInt16(value2)))
let now = NSDate()
print(value2)
print(now)
self.animateHeart()
let completion: ((Bool, NSError?) -> Void) = {
(success, error) -> Void in
if( error != nil ) {
print("Error saving HR")
} else {
print("HR saved successfully!")
}
}
self.healthStore.saveObject(sample, withCompletion: completion)
In display: print("HR saved successfully")
Can someone help me?
Thanks