Search code examples
ioscore-datansfetchedresultscontrolleruidatepickernsfetchrequest

Using a NSFetchRequest to sort sections by Date and cells Alphabetically With Timezones


I have a UITableView that is blank to start with but gets populated by the user entering in some information. The user clicks on a button in the UINavigationBar, gets taken to another View Controller and fills in text into a UITextField and selects a date from the UIDatePicker. When the user presses Save, the UITableView is updated with the use of NSFetchedResultsController and Core Data.

Within the UITableView, the date is represented by the section header titles, while the UITextField text is represented in the actual cells.

The NSFetchRequest is ordered by date and then alphabetically by person.

Issue

It's not a common scenario, but if I add in an entry in a New York Timezone with Person name A, and then add in another entry with Person name B with the same date, it will show A and then B in that order.

If I change to a London timezone and add in another entry with person name A and the same date, this new entry is now below B.

I understand why this is happening but I'm not sure what to do to fix it.

Code

This is my NSFetchReqest:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;

    if ([self.timelineSearchBar.text length] > 0) {
        NSPredicate *predName = [NSPredicate predicateWithFormat:@"whoBy.name CONTAINS[c] %@", self.timelineSearchBar.text];
        NSPredicate *predOccasion = [NSPredicate predicateWithFormat:@"occasion.title CONTAINS[c] %@", self.timelineSearchBar.text];

        NSPredicate *predSubOccasion = [NSPredicate predicateWithFormat:@"subevent.title CONTAINS[c] %@", self.timelineSearchBar.text];
        NSPredicate *compPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[predName, predOccasion, predSubOccasion]];

        [fetchRequest setPredicate:compPredicate];
    }
    // Sorting by date and by name.
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"dates.dateOfEvent" ascending:NO];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"whoBy.name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];

    fetchRequest.sortDescriptors = @[sort, sortDescriptor];

    fetchRequest.fetchBatchSize = 20;
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"sectionDateFormatter" cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

The sort descriptor is sorting by date and then name, so it makes sense that this is happening.

Here's my saving method:

- (IBAction)save:(id)sender
{
    // Start by obtaining the managedObjectContext.
    NSManagedObjectContext *context = [self managedObjectContext];

    // Insert a new transaction
    Transaction *transaction = [NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context];

    Person *enteredPerson = (Person *)[Person personWithName:self.nameTextField.text inManagedObjectContext:context];
    transaction.whoBy = enteredPerson;


     NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
     NSDateComponents *components = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:self.datePicker.date];
     NSDate *selectedDate = [cal dateFromComponents:components]; */
    NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    NSDateComponents *components = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
                                          fromDate:self.datePicker.date];

    NSDate *selectedDate = [cal dateFromComponents:components];

    Date *date = (Date *)[Date occasionWithDate:selectedDate inManagedObjectContext:context];
    transaction.dates = date;

    // Save the ManagedObjectContext
    NSError *error = nil;
    if (![context save:&error])
    {
        // Error
    }

    [self dismissViewControllerAnimated:YES completion:nil];
}

I'm not doing anything fancy with timezones, but if I set the NSDateComponent, NSCalendar and/or UIDatePicker to the [NSTimeZone localTimeZone], I get the same behaviour.

Essentially, I'd like to keep the sorting by date and then by name, but within each date, I'd like to sort just by name. It's all the same date (but just created with different timezones), so it treats it as a later time on the same time. I'm not making any use of timing.


Solution

  • Save your dates as dates in the same time zone, e.g. by transposing the date to GMT if necessary. You can add the time zone information to your data model or change the date display on the fly based on runtime settings.