How to Group by day in NSDate with NSPredicate – needed to create multiple UITableView sections

See also

Core Data Sort By Date With FetchedResultsController into sections

Is there a way to group objects from a CoreData entity by date and use them to create multiple sections on a UITableView control?

  • Can I remove previous versions of iphone support in DeviceSupport folder in Xcode 4?
  • Cordova ios error building Images.xcassets
  • UIModalPresentationPopover for iPhone 6 Plus in landscape doesn't display popover
  • Xcode free provisioning: The 'Apple Push Notification' feature is only available to users enrolled in Apple Developer Program
  • Swift - Tinder effect
  • Google analytics 3.08 IOS idfa class missing, won't collect idfa
  • Essentially, I need something equivalent to the following pseudo-SQL, where my_group_criteria() groups all tests by day:

    SELECT * FROM tests GROUP BY my_group_criteria(date)
    

    As an example, the query would group the following rows into 3 separate sections:

    [test1 | 2013-03-18 15.30.22]
    [test2 | 2013-03-18 14.30.22]
    [test3 | 2013-03-18 13.30.22]
    
    [test4 | 2013-03-17 18.30.22]
    [test5 | 2013-03-17 19.30.22]
    
    [test6 | 2013-03-15 20.30.22]
    

    As already answered in 2, NSPredicate is not for grouping entities so this may not be the way to go.

    Given this, how do I go about creating multiple sections in a UITableView based on some criteria similar to what SQL GROUP BY would do?

    I would like to avoid accessing the SQL-lite database directly if at all possible.

    Related question 1:
    NSPredicate: filtering objects by day of NSDate property

    Related question 2:
    NSPredicate something equivalent of SQL’s GROUP BY

    2 Solutions Collect From Internet About “How to Group by day in NSDate with NSPredicate – needed to create multiple UITableView sections”

    You can modify the DateSectionTitles sample project from the Apple Developer Library according to you needs.

    First, you have to modify the accessor function for the transient sectionIdentifier property to create a section identifier based on year+month+day (instead of year+month only):

    - (NSString *)sectionIdentifier {
    
        // Create and cache the section identifier on demand.
    
        [self willAccessValueForKey:@"sectionIdentifier"];
        NSString *tmp = [self primitiveSectionIdentifier];
        [self didAccessValueForKey:@"sectionIdentifier"];
    
        if (!tmp) {
            /*
             Sections are organized by month and year. Create the section identifier
             as a string representing the number (year * 10000 + month * 100 + day);
             this way they will be correctly ordered chronologically regardless of
             the actual name of the month.
             */
            NSCalendar *calendar = [NSCalendar currentCalendar];
    
            NSDateComponents *components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
                                   fromDate:[self timeStamp]];
            tmp = [NSString stringWithFormat:@"%d", [components year] * 10000 + [components month] * 100  + [components day]];
            [self setPrimitiveSectionIdentifier:tmp];
        }
        return tmp;
    }
    

    Second, the titleForHeaderInSection delegate method must be changed, but the idea is the same: extract year, month and day from the section identifer, and create a header title string from that:

    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
        id <NSFetchedResultsSectionInfo> theSection = [[fetchedResultsController sections] objectAtIndex:section];
    
        NSInteger numericSection = [[theSection name] integerValue];
        NSInteger year = numericSection / 10000;
        NSInteger month = (numericSection / 100) % 100;
        NSInteger day = numericSection % 100;
    
        // Create header title YYYY-MM-DD (as an example):
        NSString *titleString = [NSString stringWithFormat:@"%d-%d-%d", year, month, day];
        return titleString;
    }
    

    Assuming the idea is to group the array of objects into an array of arrays of objects, the approach I would take is this, start by finding the possible values for date (or whatever you want to group by) and then actually sort it out.

    NSArray *values = ...;
    NSArray *sorting = [values valueForKey:@"@distinctUnionOfObject.propertyToGroupBy"];
    NSUInteger sectionCount = [sorting count];
    NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
    for (int i = 0; i < sectionCount; i++)
        [sections addObject:[NSMutableArray array]];
    
    [values enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        // Figure out the section for this object
        NSUInteger section = [sorting indexOfObject:obj.propertyToGroupBy];
        [[sections objectAtIndex:section] addObject:obj];
    }];
    
    // sections will contain as many sections as there were unique values
    // each of those values is an array containing values that matched the criteria
    

    This can likely be optimised, but from the top of my head, seems to fit the bill.

    EDIT: After going through the question once again, the propertyToGroupBy would have to normalise the value, i.e remove time components of the date, as the grouping is done by unique values (i.e comparison is based on isEqual:)