Can we access the heart rate directly from the apple watch? I know this is a duplicate question, but no one has asked this in like 5 months. I know you can access it from the Health App but I'm not sure how "real-time" that will be.
Heart Rate Raw Data information is now available in Watchkit for watchOS 2.0.
WatchOS 2 includes many enhancements to other existing frameworks such as HealthKit, enabling access to the health sensors that access heart rate and health information in real-time.
You could check this information in the following session which is total 30 minutes presentation.If you do not want to watch entire session, then you directly jump to Healthkit API features which is in between 25-28 min:
WatchKit for watchOS 2.0 Session in WWDC 2015
Here is the source code implementation link
As stated in the HKWorkout Class Reference:
The HKWorkout class is a concrete subclass of the HKSample class.
HealthKit uses workouts to track a wide range of activities. The
workout object not only stores summary information about the activity
(for example, duration, total distance, and total energy burned), it
also acts as a container for other samples. You can associate any
number of samples with a workout. In this way, you can add detailed
information relevant to the workout.
In that given link, the following part of the code defines sample rate of heartRate
NSMutableArray *samples = [NSMutableArray array];
HKQuantity *heartRateForInterval =
[HKQuantity quantityWithUnit:[HKUnit unitFromString:#"count/min"]
doubleValue:95.0];
HKQuantitySample *heartRateForIntervalSample =
[HKQuantitySample quantitySampleWithType:heartRateType
quantity:heartRateForInterval
startDate:intervals[0]
endDate:intervals[1]];
[samples addObject:heartRateForIntervalSample];
As they state there:
You need to fine tune the exact length of your associated samples
based on the type of workout and the needs of your app. Using 5 minute
intervals minimizes the amount of memory needed to store the workout ,
while still providing a general sense of the change in intensity over
the course of a long workout. Using 5 second intervals provides a
much-more detailed view of the workout, but requires considerably more
memory and processing.
After exploring HealthKit and WatchKit Extension, My findings are as follows:
We do not need the WatchKit Extension to get the Heart Rate Data.
You just need to have an iPhone with paired Apple watch (which is obvious)
The Default Apple Watch Heart Rate monitor app updates the HealthKit data immediately only when it is in the foreground.
When the Default Apple Watch Heart Rate monitor app is in the Background, it updates the HealthKit data at the interval of 9-10 mins.
To get the Heart rate data from the HealthKit following query needs to be fired periodically.
func getSamples() {
let heathStore = HKHealthStore()
let heartrate = HKQuantityType.quantityType(forIdentifier: .heartRate)
let sort: [NSSortDescriptor] = [
.init(key: HKSampleSortIdentifierStartDate, ascending: false)
]
let sampleQuery = HKSampleQuery(sampleType: heartrate!, predicate: nil, limit: 1, sortDescriptors: sort, resultsHandler: resultsHandler)
heathStore.execute(sampleQuery)
}
func resultsHandler(query: HKSampleQuery, results: [HKSample]?, error: Error?) {
guard error == nil else {
print("cant read heartRate data", error!)
return
}
guard let sample = results?.first as? HKQuantitySample else { return }
// let heartRateUnit: HKUnit = .init(from: "count/min")
// let doubleValue = sample.quantity.doubleValue(for: heartRateUnit)
print("heart rate is", sample)
}
Please update me if anyone gets more information.
Happy Coding.
Update
I've updated your code to be clear and general, and be aware that you need to get authorization for reading HeathKit data and adding info.plist key Privacy - Health Records Usage Description
There is no direct way to access any sensors on the Apple Watch. You will have to rely on access from HealthKit.
An Apple evangelist said this
It is not possible to create a heart monitor app at this time. The
data isn't guaranteed to be sent to iPhone in real-time, so you won't
be able to determine what's going on in any timely fashion.
See https://devforums.apple.com/message/1098855#1098855
You can get heart rate data by starting a workout and query heart rate data from healthkit.
Ask for premission for reading workout data.
HKHealthStore *healthStore = [[HKHealthStore alloc] init];
HKQuantityType *type = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKQuantityType *type2 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
HKQuantityType *type3 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
[healthStore requestAuthorizationToShareTypes:nil readTypes:[NSSet setWithObjects:type, type2, type3, nil] completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"health data request success");
}else{
NSLog(#"error %#", error);
}
}];
In AppDelegate on iPhone, respond this this request
-(void)applicationShouldRequestHealthAuthorization:(UIApplication *)application{
[healthStore handleAuthorizationForExtensionWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"phone recieved health kit request");
}
}];
}
Then implement Healthkit Delegate:
-(void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"session error %#", error);
}
-(void)workoutSession:(HKWorkoutSession *)workoutSession didChangeToState:(HKWorkoutSessionState)toState fromState:(HKWorkoutSessionState)fromState date:(NSDate *)date{
dispatch_async(dispatch_get_main_queue(), ^{
switch (toState) {
case HKWorkoutSessionStateRunning:
//When workout state is running, we will excute updateHeartbeat
[self updateHeartbeat:date];
NSLog(#"started workout");
break;
default:
break;
}
});
}
Now it's time to write **[self updateHeartbeat:date]**
-(void)updateHeartbeat:(NSDate *)startDate{
//first, create a predicate and set the endDate and option to nil/none
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:nil options:HKQueryOptionNone];
//Then we create a sample type which is HKQuantityTypeIdentifierHeartRate
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
//ok, now, create a HKAnchoredObjectQuery with all the mess that we just created.
heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:0 limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
if (!error && sampleObjects.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//wait, it's not over yet, this is the update handler
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
if (!error && SampleArray.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//now excute query and wait for the result showing up in the log. Yeah!
[healthStore executeQuery:heartQuery];
}
You also have a turn on Healthkit in capbilities. Leave a comment below if you have any questions.
Related
I am trying to develop a watchOS app that collects Sensor(accelerator) data at a very high frequency(100Hz). I am using Core Data to store the data and export it to a CSV file, then send to iPhone.
The app crashed after a short time, because some thread's memory exceeded the limitation. And I also found out that the "performBackgroundTask" method creates too many threads and the CPU usage is more than 100%.
Another problem is when I fetch the data out, I got more data items than the that are actually stored in the Sqlite Database, which is quite confusing.
Below is the code and the method is called in this method([self.motionManager startDeviceMotionUpdatesToQueue:self.queue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {}])'s callback block:
- (void)workoutManager:(nonnull id)manager didReceiveAccelerationData:(nonnull YLAcceleration *)acceleration {
NSString *dateTime = [self.dateFormatter stringFromDate:[NSDate date]];
[self.coreDataStack.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
Acceleration *accData = [[Acceleration alloc] initWithContext:context];
accData.x = acceleration.xString;
accData.y = acceleration.yString;
accData.z = acceleration.zString;
accData.heartRate = self.heartRate;
accData.dateTime = dateTime;
accData.session = self.currentSession;
NSError *saveError = nil;
[context save:&saveError];
if (error) {
NSLog(#"Save data failed:%#", error);
}else{
NSLog(#"Save data succeeded:%#", [NSThread currentThread]);
}
}];
}
Thank you in advance.
In iOS 9.3 Apple release new APIs. Now developers can see if a user is currently a member of Apple Music. I'm trying to understand how it works.
My code -only first time- asks the user whether to access the music library but I don't understand how to determine in what ways you can detect if user is a member and open Apple Music to join it. These actions in Shazam works really great. How can I do something like that?
Thanks in advance!
[SKCloudServiceController requestAuthorization:^(SKCloudServiceAuthorizationStatus status) {
NSLog(#"status is %ld", (long)status);
SKCloudServiceController *cloudServiceController = [[SKCloudServiceController alloc] init];
[cloudServiceController requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
NSLog(#"%lu %#", (unsigned long)capabilities, error);
if (capabilities >= SKCloudServiceCapabilityAddToCloudMusicLibrary || capabilities==SKCloudServiceCapabilityMusicCatalogPlayback) {
NSLog(#"You CAN add to iCloud!");
} else {
NSLog(#"The ability to add Apple Music track is not there. sigh.");
}
}];
}];
You should check for the SKCloudServiceCapabilityMusicCatalogPlayback flag in capabilities. The code in your question checks whether the capabilities variable equals that flag, but you need to use bitwise operators. Here is how you'll do that.
SKCloudServiceController *controller = [SKCloudServiceController new];
[controller requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error getting SKCloudServiceController capabilities: %#", error);
} else if (capabilities & SKCloudServiceCapabilityMusicCatalogPlayback) {
// The user has an active subscription
} else {
// The user does *not* have an active subscription
}
}];
You can tell that SKCloudServiceCapabilityMusicCatalogPlayback is a flag and not just a regular constant value because the value uses bitwise operators (the "<<" shown in Apple's documentation).
SKCloudServiceCapabilityMusicCatalogPlayback = 1 << 0
https://developer.apple.com/reference/storekit/skcloudservicecapability/skcloudservicecapabilitymusiccatalogplayback?language=objc
I'm experimenting with Firebase's FDataSnapshot to pull in data and I would like it to write its data to my core data using MagicalRecord.
According to Firebases "best practice" blog I need to keep a reference to the "handle" so it can be cleaned up later on. Further, they mention to put the FDSnapshot code in viewWillAppear.
I am wanting a callback so that when its finished doing its thing to update core data.
But I'm really note sure how to do that; its doing two things and giving a return at the same time.
// In viewWillAppear:
__block NSManagedObjectContext *context = [NSManagedObjectContext MR_context];
self.handle = [self.ref observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
if (snapshot.value == [NSNull null])
{
NSLog(#"Cannot find any data");
}
else
{
NSArray *snapshotArray = [snapshot value];
// cleanup to prevent duplicates
[FCFighter MR_truncateAllInContext:context];
for (NSDictionary *dict in snapshotArray)
{
FCFighter *fighter = [FCFighter insertInManagedObjectContext:context];
fighter.name = dict[#"name"];
[context MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError *error){
if (error)
{
NSLog(#"Error - %#", error.localizedDescription);
}
}];
}
}
}];
NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:[FCFighter entityName]];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
self.fighterList = (NSArray *) [context executeFetchRequest:fr error:nil];
[self.tableView reloadData];
In the above code, the core data reading does not wait for the firebase to complete.
Thus, my query -- how would I best combine a completion handler so that when it is complete to update core data, and reload the tableview.
Many thanks
This is a common issue when working with Asynchronous data.
The bottom line is that all processing of data returned from an async call (in this case, the snapshot) needs to be done inside the block.
Anything done outside the block may happen before the data is returned.
So some sudo code
observeEvent withBlock { snapshot
//it is here where snapshot is valid. Process it.
NSLog(#"%#", snapshot.value)
}
Oh, and a side note. You really only need to track the handle reference when you are going to do something else with it later. Other than that, you can ignore the handles.
So this is perfectly valid:
[self.ref observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
//load your array of tableView data from snapshot
// and/or store it in CoreData
//reload your tableview
}
I success to get heart rate data in live without workout session on apple watch os 2. But when apple watch screen turn off, my completion block is not anymore called. I would like to continue to manage these data in live and to make my phone ring when heart rate is too low.
Maybe i can let the app on the iphone perma open and maybe it can access to the healthkit data during this workout ?
Do you think this can work ? or do you have another idea ?
Regards
Hey i found a solution :
i keep iphone app in foreground with :
[UIApplication sharedApplication].idleTimerDisabled = YES
And with the same query than apple watch (HKAnchoredObjectQuery) i can access the latest health kit data. I well get live heart rate data even when my apple watch is turn off (with a workout session)
my query
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKAnchoredObjectQuery *heartRateQuery = [[HKAnchoredObjectQuery alloc]
initWithType:type
predicate:nil
anchor:self.anchor
limit:HKObjectQueryNoLimit
resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable sampleObjects, NSArray<HKDeletedObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
if (error) {
// Perform proper error handling here...
NSLog(#"*** An error occured while performing the anchored object query. %# ***",
error.localizedDescription);
}
self.anchor = newAnchor;
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects firstObject];
if (sample) {
double value = [sample.quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
dispatch_async(dispatch_get_main_queue(), ^(void){
self.heartrateLabel.text = [NSString stringWithFormat:#"%0.0f",value];
});
NSLog([NSString stringWithFormat:#"%0.0f",value]);
[self.hkStore stopQuery:heartRateQuery];
}
}];
[self.hkStore executeQuery:heartRateQuery];
By design, watchOS 2 apps are not allowed to run while the watch screen is off. You cannot change this behavior.
Questions:
How do i release the memory used by the NSManagedObjectContext(i guess) when the number of records to be inserted to Core Data are unforeseeable, such that the memory can be efficiently used?
Here is my case:
I have a bluetooth device that will continuously send twelve sets of integer to the iOS Device on every 0.00125 seconds(the minimum interval, maximum case will be 0.002 seconds) , i should then store those integer into CoreData with the timestamp.
Data Objects and Association:
When the process start, a header record(NSManagedObject) is created as the key to retrieve the batch of received data from the bluetooth device. The object is retained as strong property during the whole period of data receiving and will remove from the property( probably set nil) when the process is ended.
Design of the NSManagedObjectContext:
All of the three ManagedObjectContext are a singleton object stored in AppDelegate
The code for creating the ManagedObjectContext are listed below:
- (NSManagedObjectContext *)masterManagedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_masterManagedObjectContext != nil) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
[_masterManagedObjectContext setUndoManager:nil];
return _masterManagedObjectContext;
}
-(NSManagedObjectContext*) backgroundManagedObjectContext{
if(_backgroundManagedObjectContext != nil){
return _backgroundManagedObjectContext;
}
_backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[_backgroundManagedObjectContext setUndoManager:nil];
[_backgroundManagedObjectContext setParentContext:[self masterManagedObjectContext]];
return _backgroundManagedObjectContext;
}
-(NSManagedObjectContext*) mainManagedObjectContext{
if(_mainManagedObjectContext !=nil){
return _mainManagedObjectContext;
}
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setUndoManager:nil];
[_mainManagedObjectContext setParentContext:[self masterManagedObjectContext]];
return _mainManagedObjectContext;
}
The import is processed in the backgroundManagedObjectContext.
The Header is created and stored by using the below code:
_header = [NSEntityDescription insertNewObjectForEntityForName:#"Header" inManagedObjectContext:_backgroundManagedObjectContext];
_header.startTime = [NSDate date];
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
The Received Data is created and stored by using the below code when the bluetooth devices fired the method:
#autoreleasepool {
ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:#"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
//Data is set here
[_header addFk_header_many_dataObject:data];
currentCount ++;
if(currentCount >=1000){
currentCount = 0;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
}
}
the received data will be stored into the managedObjectContext per 1000 data is received.
Once if i stop the process, the memory consumed is doubled, and last until i completely terminate the app.
The code to handle the end of process is listed below:
_header.endTime = [NSDate date];
_header = nil;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
[_masterManagedObjectContext performBlock:^{
NSError* mastererror;
BOOL mastersuccess = [_masterManagedObjectContext save:&mastererror];
}];
Issue:
As mentioned by Core Data Performance by Apple, using reset method of NSManagedObjectContext will remove all managed objects associated with a context and "start over" as if you'd just created it.
In my understanding, that means i can only call this method at the end of the whole process. I have tried to add reset function just after _backgroundManagedObjectContext and _masterManagedObjectContext is saved. However, the memory remain unchanged.
Illustration of the memory usage
For the case of data is received on every 0.002 seconds, 0.5MB memory increased per 1000 records is saved to backgroundManagedObjectContext. Therefore, the app will consume around 150 MB for 8 mins process time and memory will increase to 320MB when the process terminated at that time, and will retain the memory usage around 220MB.
Questions:
How do i release the memory used by the NSManagedObjectContext(i guess) when the number of records to be inserted to Core Data are unforeseeable, such that the memory can be efficiently used?
Sorry for some idiot mistakes as i am quite new in iOS. I have tried my best to search around before posting the question.
Your help is appreciated.
Thank you very much.
Remarks
I have tried the above mentioned case in no more than 10 mins process time. However, the implementation should have extended to the case for more than 1 hour process time. I still have no idea on the way of handling such case.
EDIT 1 modified the code for showing the relationship of ReceivedData and Header
EDIT 2 updated the code for the standard mentioned by #flexaddicted
Just my advice. Maybe someone could have a different approach.
In this case I would eliminate the BackgroundManagedObjectContext and I would leave only the MasterManagedObjectContext (as the parent of the main one). Since, you need a low memory profile, you should switch to mechanism that allows you to control the memory footprint of your application. So, I would created a sort of buffer that starts to collect the receive data. When the buffer has reached its limit, I would move the receive data to the MasterManagedObjectContext in order to save them into the persistent store. Here the limit of the buffer (a vector of structs or array of objects in my mind) should be tuned on the application performance. In this way you have a direct control of the objects created. So, you can throw them away whenever you have finished a bunch of imported data (where the bunch is the limit of that vector/array).
Otherwise, you can try the following approach.
#autoreleasepool {
NSMutableArray *temporary = [NSMutableArray array];
ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:#"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
// Data is set here
// Let temporary to hold a reference of the data object
[temporary addObject:data];
currentCount ++;
if(currentCount >=1000){
currentCount = 0;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
for(NSManagedObject *object in temporary) {
[_backgroundManagedObjectContext refreshObject:object mergeChanges:NO];
}
[temporary removeAllObjects];
}
}
Update 1
Can you also show where you set the relationship between ReceiveData and Header? I'm asking this because you can change the time where you set the relationship between this two entity.
Based on your modified code.
#autoreleasepool {
receivedData* data = [NSEntityDescription insertNewObjectForEntityForName:#"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
//Data is set here
[_header addFk_header_many_dataObject:data];
currentCount ++;
if(currentCount >=1000){
currentCount = 0;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
}
}
If you are able to posticipate this association on the master queue (I guess you need to set the attribute as an optional one), you can do like the following:
#autoreleasepool {
ReceivedData* data = [NSEntityDescription insertNewObjectForEntityForName:#"ReceivedData" inManagedObjectContext:_backgroundManagedObjectContext];
// Data is set here
// Move it later
//[_header addFk_header_many_dataObject:data];
currentCount ++;
if(currentCount >=1000){
currentCount = 0;
NSError *error;
BOOL success = [_backgroundManagedObjectContext save:&error];
[_backgroundManagedObjectContext reset];
}
}
P.S. receiveData *data = ... should be ReceiveData *data = .... In other words, classes should start with a capital letter.