Getting hear rate from HealthKit - ios

I have a fairly simple code that gets heart beat, and is not returning anything. No error, no warning, nothing, and nothing in the doc that I could find to help me. Am I missing something obvious ?
Here is the code :
- (void) readLastDayHeartRateMeasurements
{
NSCalendar *calendar = [NSCalendar currentCalendar] ;
NSDate *startDate = [NSDate date] ;
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay
value:-1
toDate:startDate
options:0] ;
HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate] ;
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate
endDate:endDate
options:HKQueryOptionNone] ;
self.query = [[HKSampleQuery alloc] initWithSampleType:sampleType
predicate:predicate
limit:HKObjectQueryNoLimit
sortDescriptors:nil
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error)
{
NSLog(#"Query %# got a result with %lu samples, error %#",query,(unsigned long)[results count],error) ;
[error logDetailsOfError] ;
}];
if (self.query)
{
NSLog(#"Query %# about to start",self.query) ;
[self.hkStore executeQuery:self.query] ;
NSLog(#"Query %# started",self.query) ;
}
else
{
NSLog(#"No query to execute") ;
}
}
I get the following logs :
2019-01-06 16:56:06.252736+0100 HeartBeatDetails[1128:193491] Authorization succes : 1
2019-01-06 16:56:06.253984+0100 HeartBeatDetails[1128:193491] Query <HKSampleQuery:0x2832c19a0 inactive> about to start
2019-01-06 16:56:06.254440+0100 HeartBeatDetails[1128:193491] Query <HKSampleQuery:0x2832c19a0 activating> started
2019-01-06 16:56:06.276334+0100 HeartBeatDetails[1128:193492] Query <HKSampleQuery:0x2832c19a0 deactivated> got a result with 0 samples, error (null)
I'm running from XCode directly to my phone. As I obviously have hear rates in my phone, what am I doing wrong ?
Thanks,

Shame on me !
The start date and end date are switched...
Correct code :
NSDate *endDate = [NSDate date] ;
NSDate *startDate = [calendar dateByAddingUnit:NSCalendarUnitHour
value:-1
toDate:endDate
options:0] ;

Related

HKStatisticsCollectionQuery for fetching Heart Rate Health Kit

I have been trying for a while for fetching heart rate in order to plot in a graph. As mentioned in docs heart rate can be fetched by HKStatisticsCollectionQuery. I am trying to fetch a week's data from current date.
But I am unable to get the fetched data. Here is my code below for heart rate accessing using HKStatisticsCollectionQuery :
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *interval = [[NSDateComponents alloc] init];
NSDate *anchorDate = [[NSDate alloc] init];
NSDateComponents *anchorComponents =
[calendar components:NSCalendarUnitDay | NSCalendarUnitMonth |
NSCalendarUnitYear | NSCalendarUnitWeekday fromDate:[NSDate date]];
NSDate *currentDisplayEndDate = [NSDate date];
NSDate *newDate = [calendar startOfDayForDate: currentDisplayEndDate]; NSDate *startDate = [newDate dateByAddingTimeInterval:-6*24*60*60];
anchorDate = startDate;
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:self.startDate endDate:_currentDisplayEndDate options:HKQueryOptionStrictStartDate];
HKQuantityType *quantityType =
[HKObjectType quantityTypeForIdentifier:quantityId];
// Create the query
HKStatisticsCollectionQuery *query =
[[HKStatisticsCollectionQuery alloc]
initWithQuantityType:quantityType
quantitySamplePredicate:predicate
options:HKStatisticsOptionDiscreteMax
anchorDate:anchorDate
intervalComponents: interval];
// Set the results handler
query.initialResultsHandler =
^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
if (error) {
// Perform proper error handling here
NSLog(#"*** An error occurred while calculating the statistics: %# ***",
error.localizedDescription);
}
[results
enumerateStatisticsFromDate:startDate
toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
HKQuantity *quantity = result.sumQuantity;
if (quantity) {
NSDate *date = result.startDate;
double value = [quantity doubleValueForUnit:[[HKUnit unitFromString:#"count/min"]];
// Call a custom method to plot each data point.
}
}];
};
[healthStore executeQuery:query];
My HKStatistics *results is returned as nil.Am I doing something wrong over here?
Problem in not where you thought, The results are returned with statistical query, but in case of heart rate, it does not give heart beat quantity along with that, so HKQuantity *quantity = result.sumQuantity; returns nil.
If you will check properly, you will see that results.statistics will give you some data about recorded heart rate, but no heart rate quantity, rather only, the start and end dates for the recorded data.
I would suggest, go ahead and you HKAnchoredQuery for the same, I will provide the code, here:
-(double)get_heartRates
{
//code to heart beats average, modify as needed
NSDate *startDate1 = [NSDate distantPast];
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate1 endDate:[NSDate date] options:HKQueryOptionStrictEndDate];
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
sum_Of_HeartRates=0.0;
HKAnchoredObjectQuery *heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:self.lastAnchor limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
NSLog(#"Sample counts:%ld",sampleObjects.count);
for(int i=0;i<(int)sampleObjects.count;i++)
{
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:i];
HKQuantity *quantity = sample.quantity;
double bpm_Values= [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
sum_Of_HeartRates=sum_Of_HeartRates+bpm_Values;
}
avg_heartBeats=sum_Of_HeartRates/(int)sampleObjects.count;
}];
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
new_Updated_Data =[quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
NSLog(#"new quantity:%f",new_Updated_Data);
}];
[self.healthStore executeQuery:heartQuery];
NSLog(#"updated data %f",new_Updated_Data);
return avg_heartBeats;
}

How to get daily sleep duration using Apple HealthKit?

I'm doing an app that reads daily steps and sleep data from Apple HealthKit.
For Steps, it's pretty easy because it is a HKQuantityType, so I can apply HKStatisticsOptionCumulativeSum option on it. Put the start date, end date, and date interval in, and you got it.
- (void)readDailyStepsSince:(NSDate *)date completion:(void (^)(NSArray *results, NSError *error))completion {
NSDate *today = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear fromDate:date];
comps.hour = 0;
comps.minute = 0;
comps.second = 0;
NSDate *midnightOfStartDate = [calendar dateFromComponents:comps];
NSDate *anchorDate = midnightOfStartDate;
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKStatisticsOptions sumOptions = HKStatisticsOptionCumulativeSum;
NSPredicate *dateRangePred = [HKQuery predicateForSamplesWithStartDate:midnightOfStartDate endDate:today options:HKQueryOptionNone];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 1;
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:stepType quantitySamplePredicate:dateRangePred options:sumOptions anchorDate:anchorDate intervalComponents:interval];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *result, NSError *error) {
NSMutableArray *output = [NSMutableArray array];
// we want "populated" statistics only, so we use result.statistics to iterate
for (HKStatistics *sample in result.statistics) {
double steps = [sample.sumQuantity doubleValueForUnit:[HKUnit countUnit]];
NSDictionary *dict = #{#"date": sample.startDate, #"steps": #(steps)};
//NSLog(#"[STEP] date:%# steps:%.0f", s.startDate, steps);
[output addObject:dict];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) {
NSLog(#"[STEP] %#", output);
completion(output, error);
}
});
};
[self.healthStore executeQuery:query];
}
But for Sleep it's not so straight forward. There are many things I stuck on.
First, unlike steps, sleep is a HKCategoryType. So we cannot use HKStatisticsCollectionQuery to sum it because this method only accepts HKQuantityType.
Also there are 2 value types of sleep, HKCategoryValueSleepAnalysisInBed and HKCategoryValueSleepAnalysisAsleep. I'm not sure which value is best for just the sleep duration. I'll just use HKCategoryValueSleepAnalysisAsleep only for simplicity.
Sleep data comes in an array of HKCategorySample objects. Each with start date and end date. How do I effectively combine those data, trim it to within a day, and get the daily sleep duration (in minutes) out of it? I found this DTTimePeriodCollection class in DateTool pod that may do this job, but I haven't figure it out yet.
Simply put, if anyone knows how to get daily sleep duration using Apple HealthKit, please tell me!
Check how I have did this, its working for me to get collection of the sleep data
func sleepTime() {
let healthStore = HKHealthStore()
// startDate and endDate are NSDate objects
// first, we define the object type we want
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
// You may want to use a predicate to filter the data... startDate and endDate are NSDate objects corresponding to the time range that you want to retrieve
//let predicate = HKQuery.predicateForSamplesWithStartDate(startDate,endDate: endDate ,options: .None)
// Get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// the block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit: 100000, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
// Handle the error in your app gracefully
return
}
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
let startDate = sample.startDate
let endDate = sample.endDate
print()
let sleepTimeForOneDay = sample.endDate.timeIntervalSince(sample.startDate)
}
}
}
}
}
This gives the array of entry slots.
I used this:
#import HealthKit;
#implementation HKHealthStore (AAPLExtensions)
- (void)hkQueryExecute:(void (^)(double, NSError *))completion {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now];
NSDate *startDate = [calendar dateFromComponents:components];
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];
HKSampleType *sampleType = [HKSampleType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionNone];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:0 sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (!results) {
NSLog(#"An error occured fetching the user's sleep duration. In your app, try to handle this gracefully. The error was: %#.", error);
completion(0, error);
abort();
}
double minutesSleepAggr = 0;
for (HKCategorySample *sample in results) {
NSTimeInterval distanceBetweenDates = [sample.endDate timeIntervalSinceDate:sample.startDate];
double minutesInAnHour = 60;
double minutesBetweenDates = distanceBetweenDates / minutesInAnHour;
minutesSleepAggr += minutesBetweenDates;
}
completion(minutesSleepAggr, error);
}];
[self executeQuery:query];
}
And then in view controller:
- (void)updateUsersSleepLabel {
[self.healthStore hkQueryExecute: ^(double minutes, NSError *error) {
if (minutes == 0) {
NSLog(#"Either an error occured fetching the user's sleep information or none has been stored yet.");
dispatch_async(dispatch_get_main_queue(), ^{
self.sleepDurationValueLabel.text = NSLocalizedString(#"Not available", nil);
});
}
else {
int hours = (int)minutes / 60;
int minutesNew = (int)minutes - (hours*60);
NSLog(#"hours slept: %ld:%ld", (long)hours, (long)minutesNew);
dispatch_async(dispatch_get_main_queue(), ^{
self.sleepDurationValueLabel.text = [NSString stringWithFormat:#"%d:%d", hours, minutesNew] ;
});
}
}];
}

Heart Rate data monthly

I am retrieving heart rate from Health Kit in my app. however i get the latest data from Health Kit, how can i get the data monthly wise? I mean last reading from each month.. i saw NSPredicate can be used to do so but i am not getting exactly how to do it..!! I am new to it, please help.!!
here is my code to access heart rate:
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:quantityType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (!results) {
if (completion)
{
completion(nil, error);
}
return;
}
if (completion) {
// If quantity isn't in the database, return nil in the completion block.
HKQuantitySample *quantitySample = results.firstObject;
HKQuantity *quantity = quantitySample.quantity;
completion(quantity, error);
}
}];
[self executeQuery:query];
Code for predicate:
NSDate *startDate = [NSDate dateWithYear:[NSNumber numberWithInt:2015] month:[NSNumber numberWithInt:1] day:[NSNumber numberWithInt:1] hour:[NSNumber numberWithInt:00] minute:[NSNumber numberWithInt:00] seconds:[NSNumber numberWithInt:00]];
NSDate *endDate = [NSDate dateWithYear:[NSNumber numberWithInt:2015] month:[NSNumber numberWithInt:30] day:[NSNumber numberWithInt:1] hour:[NSNumber numberWithInt:00] minute:[NSNumber numberWithInt:00] seconds:[NSNumber numberWithInt:59]];
NSPredicate *explicitTimeInterval =
[NSPredicate predicateWithFormat:#"%K >= %# AND %K < %#",
HKPredicateKeyPathEndDate, startDate,
HKPredicateKeyPathStartDate, endDate];
self.healthStore = [[HKHealthStore alloc]init];
Thanks in advance :)

How to count unique dates of CoreData objects?

I need a proper way to count how many unique days there are in CoreData objects with a property of type NSDate.
For example, I have the following:
<Object>.date = "2014-05-15 21:29:12 +0000";
<Object>.date = "2014-05-15 21:49:34 +0000";
<Object>.date = "2014-05-16 13:29:23 +0000";
<Object>.date = "2014-05-16 20:49:50 +0000";
<Object>.date = "2014-05-16 22:01:53 +0000";
<Object>.date = "2014-05-20 03:32:12 +0000";
<Object>.date = "2014-05-20 12:45:23 +0000";
<Object>.date = "2014-05-20 14:15:50 +0000";
<Object>.date = "2014-05-20 20:20:05 +0000";
In this case, the result must be 3 because there are 3 different days, 2014-05-15, 2014-05-16 and 2014-05-20
Any way to deal with this problem?
I tried with NSPredicate but I did not succeed
Thanks!
That's easy. Let me show you what I'm going to do for it.
Group your results with sort description key. This example helps you to understand how it can be realized.
https://developer.apple.com/library/ios/samplecode/DateSectionTitles/Introduction/Intro.html
And then just calculate these groups.
EDIT:
NSDate+Utils.h
- (NSDate *) dateWithoutTime
NSDate+Utils.m
-(NSDate *) dateWithoutTime
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:self];
return [calendar dateFromComponents:components];
}
some file
- (NSUInteger) someObjectsCount
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"SomeObject"];
NSString *key = #"date.dateWithoutTime";
fetchRequest.sortDescriptors = #[[[NSSortDescriptor alloc] initWithKey:key
ascending:YES]];
NSManagedObjectContext *context;
context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchedResultsController *aFetchedResultsController;
aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:key
cacheName:nil];
[aFetchedResultsController performFetch:nil];
return [[aFetchedResultsController sections] count];
}
That's all!
For Swift
One way is to use a set:
let array = ["15-06-2017", "15-08-2017", "15-06-2017", "14-06-2017",
"14-06-2017"]
let unique = Array(Set(array))
// ["15-06-2017", "15-08-2017", "14-06-2017"]
You could also create an extension that filters through the array more explicitly:
extension Array where Element : Equatable {
var unique: [Element] {
var uniqueValues: [Element] = []
forEach { item in
if !uniqueValues.contains(item) {
uniqueValues += [item]
}
}
return uniqueValues
}
}
NOTE
The unique array will be in an unspecified order, and you may need to sort it. Sometimes it's better to just do it yourself by enumerating, you could write an extension.
It might be good to make an extension :
extension Array where Element : Hashable {
var unique: [Element] {
return Array(Set(self))
}
}
There are probably more optimised ways to do what you want, but this way is quick and easy.
As of iOS 8 you can use NSCalendar's startOfDayForDate
I have the same issue in an app of mine and I've never found a predicate that would do that for me.
I currently look through all the objects in the entity and calculate the day for each date and then return an array of unique days. I am considering adding a day attribute to my entity but have not yet tested that concept.
Below is the code that I currently use. Note:
Both methods are class methods in a class called Game
The entity in my model is called Game and the attribute I want to convert to unique days is startDateTime
The returned array contains unique days in reverse sorted order (most recent day first)
If you are not concerned with the actual unique days but only the count of unique days the code is trivial to change to return the count of days ([uniqueDays count])
My code:
+ (NSArray *)allGameStartDaysInManagedObjectContext:(NSManagedObjectContext *)moc {
__block NSArray *gameDates;
// mod cannot be nil
NSParameterAssert(moc);
[moc performBlockAndWait:^{
NSError *error;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Game"];
NSArray *game = [moc executeFetchRequest:request error:&error];
// Check for errors
if (!game) {
// Log errors
NSLog(#"[%# %# %d]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
NSLog(#"Core Data error: %#", error.localizedDescription);
NSArray *errors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if (errors != nil && errors.count > 0) {
for (NSError *error in errors) {
NSLog(#" Error: %#", error.userInfo);
}
} else {
NSLog(#" %#", error.userInfo);
}
gameDates = nil;
} else if (game.count) {
// Array to hold (at most) all the days of the games in the database
NSMutableArray *days = [[NSMutableArray alloc] initWithCapacity:game.count];
for (Game *games in game) {
// Add only the day to the array
[days addObject:[Game convertDateTimetoDay:games.startDateTime]];
}
// Generate a unique set of dates
NSSet *uniqueDays = [NSSet setWithArray:days];
// Create an array from the unique set
NSMutableArray *uniqueGameDays = [NSMutableArray arrayWithArray:[uniqueDays allObjects]];
// Create the sort descriptor
NSSortDescriptor *sortOrder = [NSSortDescriptor sortDescriptorWithKey:#"self" ascending:NO];
// Sort the array
NSArray *sortedUniqueGameDays = [uniqueGameDays sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortOrder]];
gameDates = [sortedUniqueGameDays copy];
} else {
gameDates = nil;
}
}];
return gameDates;
}
+ (NSDate *)convertDateTimetoDay:(NSDate *)dateTimeToConvert {
// Get the year, month and day components (included era although this only applies to BCE)
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSEraCalendarUnit|NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:dateTimeToConvert];
// Add the date with only the selected components to the array
return [[[NSCalendar currentCalendar] dateFromComponents:components] dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
}
You should have a new attribute called "daydate" which is a date set to midnight from your current date and time.
Every time you create/modify one of your objects, operate like this:
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDateComponents *dateComps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:date];
NSDate *daydate = [calendar dateFromComponents:dateComps];
myObject.date = date;
myObject.daydate = daydate;
Then you can operate your fetch (2 options).
Option 1:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myObject" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"daydate"];
NSExpression *functionExpression = [NSExpression expressionForFunction:#"count:" arguments:#[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"count for this daydate:"];
[expressionDescription setExpression:functionExpression];
[expressionDescription setExpressionResultType:NSDoubleAttributeType];
NSAttributeDescription *attributeDesc = (entity.attributesByName)[#"daydate"];
[request setPropertiesToFetch:#[attributeDesc, expressionDescription]];
[request setPropertiesToGroupBy:#[attributeDesc]];
[request setResultType:NSDictionaryResultType];
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"array: %#", array);
NSLog(#"%lu", (unsigned long)array.count);
Option 2:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myObject" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
request.returnsDistinctResults = YES;
request.propertiesToFetch = #[#"daydate"];
request.resultType = NSDictionaryResultType;
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"array: %#", array);
NSLog(#"array count: %lu", (unsigned long)array.count);

Deleting core data objects from a specific section

I have a very weird question, but if I can explain it well, I guess the answer will not be so difficult.
This is my current scenario:
I have a tableView with six fixed sections.
The sections are populated with core data objects.
I am using a transient attribute to decide on which section the object must be shown. This procedure is done in a NSManagedObject subclass. The transient attribute is called sectionIdentifier.
Due to the reason of using a transient attribute to decide on which section should appear every object, I decided to create a special row on each section, with the intention that there will be always at least one row on every section. This option will reduce the problem to have a nil section that would throw exceptions in case of adding or deleting rows, at least this has been a problem sometimes while testing the app.
That means that at the first launch every section has one row (I will call it the special row). And then the user can add/delete rows perfectly.
The problem I have now is that I need this special row to be remove if the section has new rows. At the same time, if the user deletes the last row in the section, the app automatically creates a new special row, so the section is never empty.
I know that this is not the best way to manage it, but due to my iOS knowledge, this is the best way I found to avoid exceptions while adding or removing rows from an empty section.
What I need is to delete the special row from a section when the user has added at least one new row to the section.
The object has a string attribute called quitar and for the special rows: quitar = #"si". This way, all special rows are easy to identify.
I am using this code to delete the special row if there are new rows in the section:
NSManagedObjectContext *context = self.managedObjectContext;
NSEntityDescription *entity=[NSEntityDescription entityForName:#"ToDoItem" inManagedObjectContext:context];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
[fetch setEntity:entity];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"quitar like 'si'"];
[fetch setPredicate:predicate];
NSError *fetchError;
NSArray *fetchedData=[self.managedObjectContext executeFetchRequest:fetch error:&fetchError];
for (NSManagedObject *item in fetchedData){
[context deleteObject:item];
}
I was told that you cannot use transient attributes in a NSPredicate, and you can see in my code, that all special rows are deleted, not only the special from the desired section.
I need your help to find a way to delete only the special row from the section I want, not from all the sections.
Thank you and sorry for my english.
EDITED
Please find below the way I assign sectionIdentifier in the NSManagedObject class ToDoItem:
-(NSString *)sectionIdentifier{
[self willAccessValueForKey:#"sectionIdentifier"];
NSString *tmp = [self primitiveValueForKey:#"sectionIdentifier"];
[self didAccessValueForKey:#"sectionIdentifier"];
if (!tmp){
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger comps = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
NSDate *today = [NSDate date];
NSDate *date = self.todoDueDate;
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setCalendar:calendar];
[components setYear:2065];
[components setMonth:11];
[components setDay:12];
NSDate *dateFuturoSomeday = [calendar dateFromComponents:components];
NSCalendar *calendar1 = [NSCalendar currentCalendar];
NSDateComponents *components1 = [[NSDateComponents alloc] init];
[components1 setCalendar:calendar1];
[components1 setYear:2065];
[components1 setMonth:11];
[components1 setDay:13];
NSDate *dateFuturoCompleted = [calendar1 dateFromComponents:components1];
NSDateComponents *date1Components = [calendar components:comps
fromDate: today];
NSDateComponents *date2Components = [calendar components:comps
fromDate: date];
NSDateComponents *date3Components = [calendar components:comps
fromDate: dateFuturoSomeday];
NSDateComponents *date4Components = [calendar components:comps
fromDate: dateFuturoCompleted];
today = [calendar dateFromComponents:date1Components];
date = [calendar dateFromComponents:date2Components];
dateFuturoSomeday = [calendar dateFromComponents:date3Components];
dateFuturoCompleted = [calendar dateFromComponents:date4Components];
NSInteger daysAfterToday = [calendar components:NSDayCalendarUnit
fromDate:today toDate:date options:0].day;
// NSString *section;
if (daysAfterToday < 0) {
tmp = #"0";
} else if (daysAfterToday == 0) {
tmp = #"1";
} else if (daysAfterToday > 0 && daysAfterToday < 2) {
tmp = #"2";
}
else if ([self.todoDueDate isEqualToDate:dateFuturoSomeday]){
tmp = #"4";
}
else if ([self.todoDueDate isEqualToDate:dateFuturoCompleted]){
tmp = #"5";
}
else {
tmp = #"3";
}
[self setPrimitiveValue:tmp forKey:#"sectionIdentifier"];
}
return tmp;
}
I have checked that at the cellForRowAtIndexPath method, I am able to detect the value from the transient attribute sectionIdentifier. That means that I can use it to identify the special row item, update the value of another attribute an delete it.
ToDoItem *todoitem = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString *sec = todoitem.sectionIdentifier;
NSString *quitar = todoitem.quitar;
if ([sec isEqualToString:#"1"] && [quitar isEqualToString:#"si" ] && ( numObjectsSec1 > 1 ) ){
NSLog(#"DAS OBJEKT KANN GELĂ–SCHT WERDEN %#", todoitem.todoName);
NSManagedObjectContext *context = self.managedObjectContext;
[todoitem setValue:#"si se puede borrar" forKey:#"borrar" ];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
[context deleteObject: todoitem];
};

Resources