How to count unique dates of CoreData objects? - ios

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);

Related

Query max NSDate in core data

Is there a way to query NSDate in CoreData. For example if I want an entity with the highest NSDate value? I see that NSExpression "max:" only takes an NSNumber.
You can actually ask SQL for just that value, not the object with that value:
NSExpression *date = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDate = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:maxDate]];
NSExpressionDescription *d = [[[NSExpressionDescription alloc] init] autorelease];
[d setName:#"maxDate"];
[d setExpression:maxSalaryExpression];
[d setExpressionResultType:NSDateAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:d]];
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
} else {
if (0 < [objects count]) {
NSLog(#"Maximum date: %#", [[objects objectAtIndex:0] valueForKey:#"maxDate"]);
}
}
more detail under Fetching Managed Objects -> Fetching Specific Values in the CoreData documentation.
or
Perfomed a query, ordered on Date field DESCENDING, and using setFetchLim it:1.
Its not perfect, but at least it worked.
You can do this directly in SQLite-- without fetching everything and then filtering the result, and without the complexity of NSExpression.
To get the one object that has the max date, do something like (assuming entity name Entity and date attribute timeStamp):
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timeStamp = self.#max.timeStamp"];
fetchRequest.predicate = predicate;
Do the fetch. You'll get (at most) one result, which will be the instance with the max date.
If you want to get just the date and not the entire managed object, add this before doing the fetch:
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = #[ #"timeStamp" ];
you can get it with
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"tablename"];
fetchRequest.fetchLimit = 1;
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"yourDate" ascending:NO]];
NSError *error = nil;
id person = [managedObjectContext executeFetchRequest:fetchRequest error:&error].firstObject;

NSFetchedController: Displaying Sections out of order

I have three sections for an Events entity:
Upcoming
Today
Past
Here is FRC setup:
- (NSFetchedResultsController *)fetchedResultsController
{
if(_fetchedResultsController!=nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *firstSort = [[NSSortDescriptor alloc] initWithKey:#"modified"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:firstSort, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"sectionIdentifier"
cacheName:nil];
self.fetchedResultsController.delegate = self;
return self.fetchedResultsController;
}
Using a Transient property for the Entity, I setup the the sections as:
- (NSString *)sectionIdentifier
{
[self willAccessValueForKey:#"sectionIdentifier"];
NSString *tmp = [self primitiveSectionIdentifier];
[self didAccessValueForKey:#"sectionIdentifier"];
if (!tmp)
{
NSDate *dateToCompare = [self getUTCFormateDate:[self modified]];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* now = [NSDate date];
NSDateFormatter *format = [[NSDateFormatter alloc] init];
format.dateFormat = #"dd-MM-yyyy";
NSString *stringDate = [format stringFromDate:now];
NSDate *todaysDate = [format dateFromString:stringDate];
NSUInteger differenceInDays =
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:dateToCompare] -
[calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSEraCalendarUnit forDate:todaysDate];
NSString *sectionString;
switch (differenceInDays) {
case -1:
sectionString = #"Past";
break;
case 0:
sectionString = #"Today";
break;
case 1:
sectionString = #"Upcoming";
break;
}
tmp = sectionString;
[self setPrimitiveSectionIdentifier:tmp];
}
return tmp;
}
Currently the sections appear in descending order, where Upcoming is first, then Today, and then Past. I'd like to show Today's section first.
How can I display the sections in the following order?
Section 0: Today
Section 1: Upcoming
Section 2: Past
The only thing controlling the order is the sort descriptor you have:
NSSortDescriptor *firstSort = [[NSSortDescriptor alloc] initWithKey:#"modified"
ascending:NO];
So you need to change that. You can make it ascending, but you describe a sort order which isn't directly linked to the date.
Because what you describe is dependent upon the current date you will need to make some modifications so that you have order information stored persistently into the model and which you update each day. You can then add this as your first your sort descriptor to use that, and keep the existing sort descriptor for ordering inside each section.
The information you need to store is as per the comment from #Volker, with a simple index number for each section.

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];
};

Adding data to a one to many core data model

Having some trouble getting my head around adding data into a core data model that has a one to many relationship.
Below is my model:
RecordDate - Relationship: records - One to Many with RecordWorkout.
RecordWorkout - Relationship: recExercises - One to Many With RecordExercise.
RecordExercise - Relationship: sets - One to Many With RecordSet.
RecordSet
RecordDate holds a single string variable date. (No duplicate variable of date is to be stored)
RecordWorkout holds a single string variable recWorkoutName. (No Duplicate variable of recWorkoutName can be stored.)
RecordExercise holds a single string variable recExerciseName. (Also No duplicate variable of recExerciseName can stored.
RecordSet holds three string variables (setLog, weight, reps). (Again no duplicate variable can be stored for the setLog variable.
Objective of Code:
Objective is that at the click of a button (Log Button), When button is clicked, code needs to compare the values on screen to the values already in the data store so no duplicate values are stored.
example:
date > workoutName > exercise > (Many sets e.g.: set1, set2, set3, set4)
At the first click of the button date, workoutName exerciseName and the first set details are stored. at the second click only new set data is stored.
Below is the code i am using, but it does not seem to store all the set values, some sets get missed from the data store or just the first set is stored.
setCount = setCount + 1;
NSString *weight = weightTextField.text;
NSString *reps = repsTextField.text;
NSString *setStr = [NSString stringWithFormat:#"%d", (int)setCount];
NSMutableString *log = [NSMutableString stringWithFormat:#"%# %#%# %# %# %# %#", #"Set", setStr, #":", weight, #"kg X", reps, #"reps"];
[self.tempLog addObject:log];
[self.myTableView reloadData];
[weightTextField resignFirstResponder];
[repsTextField resignFirstResponder];
//the set is stored in a temporary mutable array (tempLog) just to show in tableview
//i would rather just store it in the actual entity and fetch from there
//Entities are initialised below
NSEntityDescription *recordDateEntity = [NSEntityDescription entityForName:#"RecordDate"
inManagedObjectContext:self.managedObjectContext];
NSEntityDescription *recordWorkoutEntity = [NSEntityDescription entityForName:#"RecordWorkout"
inManagedObjectContext:self.managedObjectContext];
NSEntityDescription *recordExerciseEntity = [NSEntityDescription entityForName:#"RecordExercise"
inManagedObjectContext:self.managedObjectContext];
NSEntityDescription *recordSetEntity = [NSEntityDescription entityForName:#"RecordSet" inManagedObjectContext:self.managedObjectContext];
//Data is fetched from the existing store to be compared with the data on screen
//This data is stored in mutable arrays (record date, recordworkouts, recordexercises, recordsets)
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RecordDate"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"date == %#", dateLabel.text];
[fetchRequest setPredicate:predicate];
self.recorddates = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
NSFetchRequest *fetchRWRequest = [[NSFetchRequest alloc] initWithEntityName:#"RecordWorkout"];
NSPredicate *predicateRW = [NSPredicate predicateWithFormat:#"recWorkoutName == %#", testLabel2.text];
[fetchRWRequest setPredicate:predicateRW];
self.recordworkouts = [[managedObjectContext executeFetchRequest:fetchRWRequest error:nil] mutableCopy];
NSFetchRequest *fetchRERequest = [[NSFetchRequest alloc] initWithEntityName:#"RecordExercise"];
NSPredicate *predicateRE = [NSPredicate predicateWithFormat:#"recExerciseName == %#", exNameLabel.text];
[fetchRERequest setPredicate:predicateRE];
self.recordexercises = [[managedObjectContext executeFetchRequest:fetchRERequest error:nil] mutableCopy];
NSFetchRequest *fetchRSetRequest = [[NSFetchRequest alloc] initWithEntityName:#"RecordSet"];
NSPredicate *predicateRSet = [NSPredicate predicateWithFormat:#"setLog == %#", setStr];
[fetchRSetRequest setPredicate:predicateRSet];
self.recordsets = [[managedObjectContext executeFetchRequest:fetchRSetRequest error:nil] mutableCopy];
if ([self.recorddates count] == 0) {
NSLog(#"No Dates Imported");
RecordDate *newRecordDate = (RecordDate *)[[NSManagedObject alloc]
initWithEntity:recordDateEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordDate.date = dateLabel.text;
RecordWorkout *newRecordWorkout = (RecordWorkout *)[[NSManagedObject alloc]
initWithEntity:recordWorkoutEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordWorkout.recWorkoutName = testLabel2.text;
[newRecordDate addRecordsObject:newRecordWorkout];
RecordExercise *newRecordExercise = (RecordExercise *)[[NSManagedObject alloc] initWithEntity:recordExerciseEntity insertIntoManagedObjectContext:self.managedObjectContext];
newRecordExercise.recExerciseName = exNameLabel.text;
[newRecordWorkout addRecExercisesObject:newRecordExercise];
RecordSet *newRecordSet = (RecordSet *)[[NSManagedObject alloc]
initWithEntity:recordSetEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordSet.setLog = setStr;
newRecordSet.weight = weight;
newRecordSet.reps = reps;
[newRecordExercise addSetsObject:newRecordSet];
NSLog(#"Set number one should be added to model: %#", setStr);
} else {
NSLog(#"Dates Are Imported");
RecordDate *prevRecordDate = (RecordDate *)[self.recorddates objectAtIndex:0];
if ([self.recordworkouts count] == 0) {
RecordWorkout *newRecordWorkout = (RecordWorkout *)[[NSManagedObject alloc]
initWithEntity:recordWorkoutEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordWorkout.recWorkoutName = testLabel2.text;
[prevRecordDate addRecordsObject:newRecordWorkout];
RecordExercise *newRecordExercise = (RecordExercise *)[[NSManagedObject alloc] initWithEntity:recordExerciseEntity insertIntoManagedObjectContext:self.managedObjectContext];
newRecordExercise.recExerciseName = exNameLabel.text;
[newRecordWorkout addRecExercisesObject:newRecordExercise];
RecordSet *newRecordSet = (RecordSet *)[[NSManagedObject alloc]
initWithEntity:recordSetEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordSet.setLog = setStr;
newRecordSet.weight = weight;
newRecordSet.reps = reps;
[newRecordExercise addSetsObject:newRecordSet];
} else {
RecordWorkout *prevRecordWorkout = [self.recordworkouts objectAtIndex:0];
if ([self.recordexercises count] == 0) {
RecordExercise *newRecordExercise = (RecordExercise *)[[NSManagedObject alloc] initWithEntity:recordExerciseEntity insertIntoManagedObjectContext:self.managedObjectContext];
newRecordExercise.recExerciseName = exNameLabel.text;
[prevRecordWorkout addRecExercisesObject:newRecordExercise];
RecordSet *newRecordSet = (RecordSet *)[[NSManagedObject alloc]
initWithEntity:recordSetEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordSet.setLog = setStr;
newRecordSet.weight = weight;
newRecordSet.reps = reps;
[newRecordExercise addSetsObject:newRecordSet];
} else {
RecordExercise *prevRecordExercise = [self.recordexercises objectAtIndex:0];
if ([self.recordsets count] == 0) {
RecordSet *newRecordSet = (RecordSet *)[[NSManagedObject alloc]
initWithEntity:recordSetEntity
insertIntoManagedObjectContext:self.managedObjectContext];
newRecordSet.setLog = setStr;
newRecordSet.weight = weight;
newRecordSet.reps = reps;
[prevRecordExercise addSetsObject:newRecordSet];
} else {
NSLog(#"Sets Already Imported");
}
}
}
NSError *errr;
if (![self.managedObjectContext save:&errr])
{
NSLog(#"Error saving context: %#", errr);
}
}
I got a feeling i can achieve what I am after using a much better organised code.
Thanks for any help offered.
ps. the code above is in the logButtonPressed method
It can be so much simpler.
1) For fetching, it is sufficient to fetch just the dates and let Core Data take care of fetching the other stuff. Much more efficient und much less code. To "fetch" the related entities:
NSSet *workouts = date.workouts;
NSSet *exercises = aWorkout.exercises;
NSSet *sets = anExercise.sets;
2) For checking if an item exists, e.g. dates just follow this pattern:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Date"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"%K = %#", attributeName, theDateStringToBeChecked];
request.fetchLimit = 1;
NSArray *dates = [self.managedObjectContext executeFetchRequest:request];
NSManagedObject *objectToUse = dates.count ? dates[0] : [NSEntityDescription
insertNewObjectForEntityName:"Date"
inManagedObjectContext:self.managedObjectContext];
You could greatly reduce your code by constructing a method that takes the entity string, the attribute string and the value to check. It would have a prototype like this:
-(NSManagedObject*)existingOrNewObjectWithEntityName:(NSString*)name
attribute:(NSString*)attribute value:(NSString*)value;
Because your objects are subclasses of NSManagedObject you could then simply cast them to the right type.
Finally, don't forget to call [self.managedObjectContext save:&error]; to persist your changes.

Aggregate Operation on Core Data in Table View with count and grouping by date

I have a Core Data database with an entity "Event" and an attribute "eventDate" with "Date" type.
The task is showing events grouping by date and showing the number (#"count") of events date by date. For example eventDate (mm-dd-yyyy) data:
events eventDate
event 1 01-01-2013
event 2 01-01-2013
event 3 01-02-2013
event 4 01-03-2013
...
Result in table view:
date count
01-01-2013 2
01-02-2013 1
01-03-2013 1
...
Don't like to fetch all database as it could be big so I use aggregate operation not to fetch every object.
The code is working without error but the resulting array is empty.
My guess the problem is the grouping with date type attribute and the NSAttribute/attributeType but couldn't make it work.
The code in the Event's category file is:
+ (NSArray *)aggregateOperationOnEvent:(NSString *)attributeName withFunction:(NSString *)function withPredicate:(NSPredicate *)predicate inManagedObjectContext: (NSManagedObjectContext*)context {
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
NSAttributeDescription* attributeDesc = [entity.attributesByName objectForKey:attributeName];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: attributeName];
NSExpression *functionExpression = [NSExpression expressionForFunction: [NSString stringWithFormat:#"%#:", function] arguments: [NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: function];
[expressionDescription setExpression: functionExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
[request setPropertiesToFetch:[NSArray arrayWithObjects:attributeDesc, expressionDescription, nil]];
[request setPropertiesToGroupBy:[NSArray arrayWithObject:attributeDesc]];
[request setResultType:NSDictionaryResultType];
if (predicate != nil)
[request setPredicate:predicate];
NSError* error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
return results;
}
Aggregating on a NSDate is not a good idea, since it's backed by a floating point type. Things might be easier if you store something like days since reference date as an int32_t. You need to use NSDateComponents:
#implementation NSDate (Days)
- (NSInteger)daysSinceReferenceDate;
{
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comp = [cal components:NSDayCalendarUnit fromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0] toDate:self options:0];
return [comp day];
}
#end
The only problem was when I transferred the "results" array to another array the latter's allocation code accidentally commented out, this is why I got empty array in the NSLog. So the GOOD NEWS it's possible to use aggregate operation on DATE attribute and the solution is above. The "results" array's printout:
2013-02-13 19:19:48.063 YourAppsName[35147:c07] -------- results = (
{
count = 2;
eventDate = "2013-01-01 00:00:00 +0000";
},
{
count = 1;
eventDate = "2013-01-02 00:00:00 +0000";
},
{
count = 1;
eventDate = "2013-01-03 00:00:00 +0000";
}
)

Resources