I am working on my first iPhone App: a simple app showing the heartRate results from HealthKit in a nice way. My first step is to show the results as a raw text. But unfortunately I'm getting an exception at the following line, telling me: "thread 1 signal SIGABRT". Does someone know, what I did wrong and hint me in a direction?
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
The rest of the code looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Set up an HKHealthStore, asking the user for read/write permissions. The profile view controller is the
// first view controller that's shown to the user, so we'll ask for all of the desired HealthKit permissions now.
// In your own app, you should consider requesting permissions the first time a user wants to interact with
// HealthKit data.
if ([HKHealthStore isHealthDataAvailable]) {
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
}];
}
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
// Since we are interested in retrieving the user's latest sample
// we sort the samples in descending order by end date
// and set the limit to 1
// We are not filtering the data, and so the predicate is set to nil.
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
// construct the query & since we are not filtering the data the predicate is set to nil
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightType predicate:nil limit:1 sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
// if there is a data point, dispatch to the main queue
if (results) {
dispatch_async(dispatch_get_main_queue(), ^{
HKQuantitySample *quantitySample = results.firstObject;
// pull out the quantity from the sample
HKQuantity *quantity = quantitySample.quantity;
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
_HeartRateResults.text = [NSString stringWithFormat:#"%# lbs", [NSNumberFormatter localizedStringFromNumber:#(usersBeatsPerMinute) numberStyle:NSNumberFormatterNoStyle]];
});
}
}];
// do not forget to execute the query after its constructed
[_healthStore executeQuery:query];}
There was a comment in the documentation ("These samples use count/time units") I didn't quite understand, so I did a little searching and tried it out and was able to get a value I manually put into the Health app using this:
double rate = [mostRecentQuantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
I haven't seen unitDividedByUnit before. Here's the article I pulled it from.
Related
In my app, I create events in an EKCalendar. I fetch the events online, and in order to refresh the events, I want to first delete the calendar (if it exists), recreate it, and then put the new events in there.
To instantiate the calendar I use
- (EKCalendar *)calendar {
if (!_calendar) {
NSArray *calendars = [self.store calendarsForEntityType:EKEntityTypeEvent];
NSString *calendarTitle = #"MyCalendar";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title matches %#", calendarTitle];
NSArray *filtered = [calendars filteredArrayUsingPredicate:predicate];
if ([filtered count]) {
_calendar = [filtered firstObject];
} else {
_calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.store];
_calendar.title = calendarTitle;
_calendar.source = self.store.defaultCalendarForNewEvents.source;
NSError *calendarErr = nil;
BOOL calendarSuccess = [self.store saveCalendar:_calendar commit:YES error:&calendarErr];
if (!calendarSuccess) {
NSLog(#"Calendar Error = %#", [calendarErr localizedDescription]);
}
}
}
return _calendar;
}
To delete the calendar, I use
-(IBAction)deleteCalendar{
NSError *error = nil;
[self.store removeCalendar:_calendar commit:YES error:&error];
}
Both methods work fine individually.
So, when I start the creation of events, I do the following:
[self deleteCalendar];//delete calendar and its events, in case it already exists
[self calendar];//create calendar
[self importEvents];//put events in calendar
Now, what I observe is the following:
On the first run of the app
a calendar is created
events are imported. (This is expected, and works just fine)
While the app is running, I trigger the above methods again with a button. With the following, for me puzzling, result:
the calendar is deleted (expected result)
NO calendar is created (WHY? that is my main question).The "if (!_calendar)" part of the method is considered FALSE, and nothing is executed.
The 'importEvents' method runs through its regular hoopla, without any apparent errors, although I would expect something like a 'no source' error.
Please advise.
UPDATE:
This could be an indicator of what is happening, but I still don't get it:
After a while, the events appear in a different calendar, i.e. not the calendar called 'myCalendar', but another, iCloud based calendar, apparently the one that at that point is the defaultCalendarForNewEvents. However, that also doesn't make any sense to me.
OK, so, what is happening:
I have deleted the Calendar from the store, effectively, but a reference to that calendar actually was still hanging around in my app.
I solved it as follows:
-(IBAction)deleteCalendar:(id)sender{
NSError *error = nil;
if(_calendar){
[self.store removeCalendar:_calendar commit:YES error:&error];
}
_calendar = nil;
}
I hope this is useful to someone
I am using Azure Mobile Service as a backend for an iOS application. I have set up everything to work with offline sync which allows me to view, add, or modify data even when there is no network connection. I am running into a problem when I add a new object into a table. The add works well locally but when I synchronize data it creates a duplicate item on the local database with a slightly different objectId. The created item is not duplicated on the server side.
Here's how I am setup. By the way, thanks to #TheBasicMind for posting this model.
Here's a link to his explanation of the model: enter link description here
Here's what I do to setup the sync context and sync table:
// Initialize the Mobile Service client with your URL and key
MSClient *client = self.hpc.client;
NSManagedObjectContext *context = self.hpc.syncContext;
MSCoreDataStore *store = [[MSCoreDataStore alloc] initWithManagedObjectContext:context];
client.syncContext = [[MSSyncContext alloc] initWithDelegate:syncDelegate dataSource:store callback:nil];
// Add a Mobile Service filter to enable the busy indicator
self.client = [client clientWithFilter:self];
// Create an MSSyncTable instance to allow us to work with the Athlete table
self.syncAthleteTable = [self.client syncTableWithName:#"Athlete"];
Here's how I add a record for the moment:
NSDictionary *newItem = #{#"firstname": firstname, #"lastname": lastname, #"laterality" : laterality};
[self.athletesService addItem:newItem completion:^{
NSLog(#"New athlete added");
}];
-(void)addItem:(NSDictionary *)item completion:(CompletionBlock)completion
{
// Insert the item into the Athlete table
[self.syncAthleteTable insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
The add works as expected and it is added in a UITableView as I have an NSFetchedResultsController listening on my Main Context.
Here's where the problem occurs. When I synchronize data with the server using this function:
-(void)syncData:(CompletionBlock)completion
{
// push all changes in the sync context, then pull new data
[self.client.syncContext pushWithCompletion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self pullData:completion];
}];
}
-(void)pullData:(CompletionBlock)completion
{
MSQuery *query = [self.syncAthleteTable query];
// Pulls data from the remote server into the local table.
// We're pulling all items and filtering in the view
// query ID is used for incremental sync
[self.syncAthleteTable pullWithQuery:query queryId:#"allAthletes" completion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self refreshDataOnSuccess:completion];
}];
}
- (void) refreshDataOnSuccess:(CompletionBlock)completion
{
MSQuery *query = [self.syncAthleteTable query];
[query readWithCompletion:^(MSQueryResult *results, NSError *error) {
[self logErrorIfNotNil:error];
NSLog(#"Data that pulled from local store: ");
for ( NSDictionary *dict in results.items ) {
NSLog(#"%# %#", [dict objectForKey:#"firstname"], [dict objectForKey:#"lastname"] );
}
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
After the synchronization the NSFetchedResultsChangeInsert is called a second time for the same record with a slightly different objectID. Here's an example of the first and second objectIDs:
tD7ADE77E-0ED0-4055-BAF6-B6CF8A6960AE9
tD7ADE77E-0ED0-4055-BAF6-B6CF8A6960AE11
I am stuck here.
Any help is highly appreciated. Thank you!
In the past, when I've seen this happen, its because the "id" field the client is sending was being changed or ignored by the server logic.
Locally the store finds the object in core data using that field, so a change to it could result in the client SDK thinking it needs to insert a new object and not update an existing one.
One easy way to confirm this, is by using the tableOperation:complete: method on the data delegate and comparing the "id" column between the item originally and that being returned by operation execute.
I am able to access Workout data using workout session but unable to do the same with others such as accessing Height,Weight,Dietary Water, Body Temperature,Blood Pressure etc.
Also i am able to access heart rate but unable to access body temp. Both of them are same vital sign identifiers.
Is it that watch can access only Workout data as mentioned in WWDC 2015 video?
Sample Code:
-(void)bodyTempForLabel :(WKInterfaceLabel *)bodyTempLabel {
HKSampleType *bodyTemp = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];
[self readMostRecentSampleType:bodyTemp withCompletion:^(HKQuantitySample *quantitySample, NSError *error) {
if(error) {
NSLog(#"Error Reading Weight From health Kit");
}
self.bodyTemp = quantitySample;
double bodyTempinDegree = [[self.bodyTemp quantity] doubleValueForUnit:[HKUnit unitFromString:[NSString stringWithFormat:#"%#C", #"\u00B0"]]];
dispatch_async(dispatch_get_main_queue(), ^{
[bodyTempLabel setText:[NSString stringWithFormat:#"%f",bodyTempinDegree]];
});
}];
}
-(void)readMostRecentSampleType : (HKSampleType *)sampleType withCompletion:(void(^)(HKQuantitySample *quantitySample,NSError *error))recentSample {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierEndDate ascending:NO];
HKQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:nil limit:HKObjectQueryNoLimit sortDescriptors:#[sortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) {
if(!error) {
// No results retuned array empty
HKQuantitySample *mostRecentSample = results.firstObject;
recentSample(mostRecentSample,error);
}
}];
[_healthStore executeQuery:sampleQuery];
}
Any help would be appreciated. Thanks!!!
It seems you'll need to use a real device to debug. I'm unable to get any value from HK when running the simulator but it works fine in the Apple Watch. (Using XCode 7 Beta 5).
The apple watch has access to all health kit types (though only a subset of the data). Has your app asked for permission for all of those types? Each type you want to read or write needs to be explicitly asked for when you setup your health store. For example, to read energy burned, distance, and heart rate you need to include:
let typesToRead = Set([
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)!,
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!,
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!
])
self.healthStore.requestAuthorizationToShareTypes(typesToShare, readTypes: typesToRead) { success, error in
// ...
}
With Watch OS 2.0 developers are supposed to be allowed to access heart beat sensors....
I would love to play a bit with it and build a simple prototype for an idea I have, but I can't find anywhere info or documentation about this feature.
Can anyone point me on how to approach this task? Any link or info would be appreciated
Apple isn't technically giving developers access to the heart rate sensors in watchOS 2.0. What they are doing is providing direct access to heart rate data recorded by the sensor in HealthKit. To do this and get data in near-real time, there are two main things you need to do. First, you need to tell the watch that you are starting a workout (lets say you are running):
// Create a new workout session
self.workoutSession = HKWorkoutSession(activityType: .Running, locationType: .Indoor)
self.workoutSession!.delegate = self;
// Start the workout session
self.healthStore.startWorkoutSession(self.workoutSession!)
Then, you can start a streaming query from HKHealthKit to give you updates as HealthKit receives them:
// This is the type you want updates on. It can be any health kit type, including heart rate.
let distanceType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
// Match samples with a start date after the workout start
let predicate = HKQuery.predicateForSamplesWithStartDate(workoutStartDate, endDate: nil, options: .None)
let distanceQuery = HKAnchoredObjectQuery(type: distanceType!, predicate: predicate, anchor: 0, limit: 0) { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle when the query first returns results
// TODO: do whatever you want with samples (note you are not on the main thread)
}
// This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
distanceQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle update notifications after the query has initially run
// TODO: do whatever you want with samples (note you are not on the main thread)
}
// Start the query
self.healthStore.executeQuery(distanceQuery)
This is all described in full detail in the demo at the end of the video What's New in HealthKit - WWDC 2015
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{
__weak typeof(self) weakSelf = self;
//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.
You may use HKWorkout, which is part of the HealthKit framework.
Many of software kits for iOS are now available for watchOS, such as HealthKit. You can use HealthKit (HK) functions and classes in order to calculate burned calories, find heart rate, etc. You can use HKWorkout to calculate everything about workouts and access the related variables such as heart rate, just like you did with iOS before. Read developer documentations from Apple in order to learn about HealthKit. They can be found in developer.apple.com.
Here's my code for retrieving list of YouTube videos from a particular channel:
GTLServiceYouTube *service;
self.vidInfos = [[NSMutableArray alloc] init];
service = [[GTLServiceYouTube alloc] init];
service.APIKey = #"my-api-key";
GTLQueryYouTube *query1 = [GTLQueryYouTube queryForPlaylistItemsListWithPart:#"id,snippet"];
query1.playlistId = #"the-playlist-id-from-utube-channel";
query1.maxResults = 50;
[service executeQuery:query1 completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (!error) {
GTLYouTubePlaylistItemListResponse *playlistItems = object;
for (GTLYouTubePlaylistItem *playlistItem in playlistItems) {
[self.vidInfos addObject:playlistItem];
}
[self.tableView reloadData];
}else {
NSLog(#"%#", error);
}
}];
And, in cellForRowAtIndexPath method:
GTLYouTubePlaylistItem *itemToDisplay = [self.vidInfos objectAtIndex:indexPath.row];
cell.textLabel.text = itemToDisplay.snippet.title;
The query accepts max 50 as the maximum result limit. But I need to display the entire list, which is about 250 videos.
How do i do it? I read about using pageTokens, but I couldn't find any sample or code on how to use pageTokens, where to get them and where to pass them?
A GTLYouTubeListResponse, that you receive after making a query, have an NSString property that is called nextPageToken.
This property indicates the 'address' of the 'next page', in case you have several 'pages' of search results,
(Meaning, the amount of search results is higher than the amount you've set in the maxResults property, which, as you've said, have a 50 results limit)
So, using your question as an example of 250 results total, you have 5 search results 'pages' of 50 search results on each 'page'.
GTLYouTubeQuery have a corresponding pageToken property, which 'tells' the query what 'page' of the results you want to receive.
Could be another ways to achieve that, but this was just at the top of my head
and I think this is pretty simple and straightforward way of achieving this,
Anyway, the example below uses your code to demonstrate how this property can be used.
IMPORTANT NOTE !!!
In the code below, I'm 'looping' through ALL of the search results,
This is just for illustration purposes,
In your app, you would probably also want to create your own custom limit,
So if the user searches for a general keyword, that have TONS of results, you won't try to fetch them all which, among other disadvantages, will probably be more than he will ever read, make an unnecessary network and memory usage, and will 'hog' your google developer points (or whatever it is called, when it 'costs' you to make google API calls)
So, if we'll use your original code-
// First, make GTLServiceYouTube a property, so you could access it thru out your code
#interface YourViewControllerName ()
#property (nonatomic, strong) GTLServiceYouTube *service;
#end
// don't forget to still initialise it in your viewDidLoad
self.vidInfos = [[NSMutableArray alloc] init];
service = [[GTLServiceYouTube alloc] init];
service.APIKey = #"my-api-key";
GTLQueryYouTube *query1 = [GTLQueryYouTube queryForPlaylistItemsListWithPart:#"id,snippet"];
query1.playlistId = #"the-playlist-id-from-utube-channel";
query1.maxResults = 50;
// After you've created the query, we will pass it as a parameter to our method
// that we will create below
[self makeYoutubeSearchWithQuery:query1];
// Here we create a new method that will actually make the query itself,
// We do that, so it'll be simple to call a new search query, from within
// our query's completion block
-(void)makeYoutubeSearchWithQuery:(GTLQueryYouTube *)query {
[service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (!error) {
GTLYouTubePlaylistItemListResponse *playlistItems = object;
for (GTLYouTubePlaylistItem *playlistItem in playlistItems) {
[self.vidInfos addObject:playlistItem];
}
[self.tableView reloadData];
}else {
NSLog(#"%#", error);
}
// Here we will check if our response, has a value in the nextPageToken
// property, meaning there are more search results 'pages'.
// If it is not nil, we will just set our query's pageToken property
// to be our response's nextPageToken, and will call this method
// again, but this time pass the 'modified' query, so we'll make a new
// search, with the same parameters as before, but that will ask the 'next'
// page of results for our search
// It is advised to add some sort of your own limit to the below if statement,
// such as '&& [self.vidInfos count] < 250'
if(playlistItems.nextPageToken) {
query.pageToken = playlistItems.nextPageToken;
[self makeYoutubeSearchWithQuery:query];
} else {
NSLog(#"No more pages");
}
}];
}
Good luck mate.