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.
Related
I want to perform a predicate based on a stored attribute's (NSDate) month.
I understand that comparing a start and end date (manually set) allows this.
But I feel like a custom method in the Managed Object's class should allow this fairly easily as well? Well, I tried, but I can't seem to access a simple method by having the predicate fire it. Is this in any way possible?
Managed Object files:
.h file:
#property (nonatomic,retain) NSNumber *monthOfDate;
-(NSNumber *)monthOfDate;
.m file:
#synthesize monthOfDate;
-(NSNumber *) monthOfDate
{
NSDate *date = [self start];
NSDateComponents *components = [[NSCalendar currentCalendar]components: NSCalendarUnitMonth fromDate:date];
NSNumber *month = [NSNumber numberWithInteger:[components month]];
return month;
}
This is my predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"client == %# AND monthOfDate == %d", clientMO,int 6];
It returns an error: keypath monthOfDate not found in entity. I presume because it's not an actual attribute? How can I make it detectable?
Update
The .h and .m files above are from the auto-generated Event class (NSmanaged object). I might move my custom code to a category later but so far no db model revisions are necessary.
Below is the FetchedResultsController (in a uiviewcontroller class) setup with mentioned predicate:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortfix = [[NSSortDescriptor alloc]initWithKey:#"timeStamp" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortfix,nil]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"client == %# AND monthOfDate == %d", clientMO,int 6];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:sectionKeyPathProperty
cacheName:nil];
//sectionKeyPathProperty is a variable that let's me choose one of multiple transient properties I have created for creating relevant sections.
self.fetchedResultsControllerClient = theFetchedResultsController;
_fetchedResultsControllerClient.delegate = self;
return _fetchedResultsControllerClient;
This turns out to only be possible when the MO's have already been loaded into memory. (fetched)
In my case with a FRC this is not the case thus I will have to create a separate month attribute that is saved on MO creation.
Thanks to user Run Loop for responding on a 4 year old question!
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);
I am trying to reload my tableview with the parameters received from changing a UIDatepicker. Currently I have the following method that captures the data and sends it to the UIButton (which works). I am trying to reset the container view that houses the UITableViewController which is called DisplayTableViewController but it is not reloading the NSFetchController. Any suggestions about how to debug or fix this issue?
-(void)changeDate:(UIDatePicker *)sender {
//NSDate Formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"ddMMyyyy"];
//Convert to string
NSString *stringFromDate = [dateFormatter stringFromDate:sender.date];
//Send to UIButton
[dateLabel setTitle:stringFromDate forState:UIControlStateNormal];
DisplayTableViewController *tbc = (DisplayTableViewController *)self.childViewControllers[0];
[tbc.tableView reloadData];
}
EDIT
if ([[segue identifier] isEqualToString:#"ShowDisplayResults"])
{
//Format Date for Seague
NSDateFormatter *todaysdate = [[NSDateFormatter alloc] init];
[todaysdate setDateFormat:#"ddMMyyyy"];
//Convert to string for Seague
NSString *stringFromDate = [todaysdate stringFromDate:[NSDate date]];
// Store the text we entered to dateSeague
dateSeague =stringFromDate;
// Get reference to the destination view controller
DisplayTableViewController *targetVC = [segue destinationViewController];
// Pass any objects to the view controller here, like...
targetVC.dateSeague = dateSeague;
targetVC.Ex = self.Ex;
}
NSFETCHCONTROLLER IN CONTAINER
- (NSFetchedResultsController *)fetchedResultsController {
NSLog(#"Test Date for second seague = %#", dateSeague);
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Query Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"WorkoutDetails"inManagedObjectContext:managedObjectContext];
//Fetch Entity
[fetchRequest setEntity:entity];
//Sort Returned Values
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"weight" ascending:NO];
//Fetch Sorted Array
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
//Set Return Size
[fetchRequest setFetchBatchSize:20];
//Predicate Results Based on ID
NSPredicate *sortName = [NSPredicate predicateWithFormat:#"exid == %#", Ex.name];
//Get NSDate format from String
NSString *stringFromDate = dateSeague;
NSDateFormatter *df = [[NSDateFormatter alloc] init];
//Convert Back to NSDate
[df setDateFormat:#"ddMMyyyy"];
NSDate *inputedDate = [df dateFromString: stringFromDate];
//Set Predicate
NSPredicate *sortDate = [NSPredicate predicateWithFormat:#"date == %#", inputedDate];
//Compound Predicate Array
NSPredicate *placesPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:sortName, sortDate, nil]];
//Fetch Predicate Compound Array
[fetchRequest setPredicate:placesPredicate];
//Continue Fetching!
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName: nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
First of all self.childViewControllers[0] will get you the first view controller on the view controller hierarchy and you want this only if you have one view controller that contains another view controller.
Second it's not a good practice to use this approach since you don't really know the state of the view controller when you call this and it's possible that the view controller at that index won't be the same class as you expected.
So I suggest that you implement a delegate for your view controller or view that holds the date picker and when the date is changed and you do all the things that you want to do with the date, call the delegate. If you don't want to do anything with the date, you just want to pass it to the view controller, then you can set the date picker delegate to be your DisplayTableViewController
As a side note, I don't know the hierarchy that you have right now and if you don't find my answer helpful please add more details to your question (the hierarchy and the flow that you want to get)
Are query params for the NSFetchRequest of the NSFetchController changing? If so, make sure that you use deleteCacheWithName if it is using a cache, and then reconfigure the NSFetchController.
I have an NSArray that is extracted from core data. The entities in the array have a date attribute (of type NSDate ofcourse). What I want to do is this:
The array needs to be displayed in a table view,
the sections, and their titles are months , of the objects in the array
For example if I have 3 objects (april 1, april 3 & july 7) there should be 2 sections:
- april 2012(2 obj)
- july 2012 (1 obj)).
How do I split the array like this?
Try using a NSFetchedResultsController and a custom sectionNameKeyPath which should be a method in your NSManagedObject subclass.
The fetchedResultsController can be set up as following:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"MyObject"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES];
request.sortDescriptors = #[sortDescriptor];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:#"monthAsString" cacheName:nil];
You need to implement the monthAsString method in your managedObject subclass. Allocate the NSDateFormatter only once, because it won't perform well if you allocate a new instance for every call.
- (NSString *)monthAsString {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"MMMM yyyy";
});
NSString *dateString = [formatter stringFromDate:self.date];
return dateString;
}
I know that fetched results controller have the section name key path can divide fetched results into sections.
But how could I divide NSDate into sections for each day or each month?
Or any other ways to solve this problem?
Thanks.
What you need to do is to create a transient property on your data object, and then sort your fetched results accordingly. For a TVGuide I've worked on, I needed to sort results by airDay, and ended up sorting the events by startDate, and using the transient property for section key name path:
In Event.m:
-(NSString*) airDay
{
NSDateFormatter *dayFormatter=[[NSDateFormatter alloc] init];
[dayFormatter setLocale:[NSLocale currentLocale]];
[dayFormatter setDateStyle: NSDateFormatterMediumStyle];
[dayFormatter setDoesRelativeDateFormatting: YES];
return [dayFormatter stringFromDate:self.startDate];
}
The matching fetchrequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:[Database db].managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"startDate"
ascending:YES],
nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[Database db].managedObjectContext
sectionNameKeyPath:#"airDay"
cacheName:#"SearchEvents"];
Please refer to the Apple sample code of "DateSectionTitles", you can search this in the Xcode help.
it helps a lot!!