mvasco
mvasco

Reputation: 5101

Core data objects in different tableView sections

I have a tableView with expandable/collapsable sections and fixed section titles, which I want to maintain. This is the code to do that:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 6;
}


+ (NSString*) titleForHeaderForSection:(int) section
{
    switch (section)
    {
        case 0 : return @"Overdue";
        case 1 : return @"Today";
        case 2 : return @"Tomorrow";
        case 3 : return @"Upcoming";
        case 4 : return @"Someday";
        case 5 : return @"Completed";
       // default : return [NSString stringWithFormat:@"Section no. %i",section + 1];
    }
}

For the moment, I am populating the rows with following code:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    switch (section)
    {
        case 2 : return 1;
        case 3 : return 30;
        default : return 8;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }

    // Configure the cell.

    switch (indexPath.row)
    {
        case 0 : cell.textLabel.text = @"First Cell"; break;
        case 1 : cell.textLabel.text = @"Second Cell"; break;
        case 2 : cell.textLabel.text = @"Third Cell"; break;
        case 3 : cell.textLabel.text = @"Fourth Cell"; break;
        case 4 : cell.textLabel.text = @"Fifth Cell"; break;
        case 5 : cell.textLabel.text = @"Sixth Cell"; break;
        case 6 : cell.textLabel.text = @"Seventh Cell"; break;
        case 7 : cell.textLabel.text = @"Eighth Cell"; break;
        default : cell.textLabel.text = [NSString stringWithFormat:@"Cell %i",indexPath.row + 1];
    }

    //cell.detailTextLabel.text = ...;

    return cell;
}

But I want to populate the rows from a core data entity, this is my NSFetchedResultsController:

- (NSFetchedResultsController *)fetchedResultsController {

    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription
                                   entityForName:@"ToDoItem" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                              initWithKey:@"tdText" ascending:NO];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
                                                   cacheName:@"Root"];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;

}

Core data entity is 'ToDoItem', and what I finally want is that each object appears inside the right section on the tableView. I need your help and advice.


To make my question a little bit clearer, I would create an attribute called tdDate, and this attribute should be the filtering key to decide under which section should the object be shown. Taking into account that an object with the current date will appear under the TODAY section today, tomorrow it should appear under the OVERDUE section...

Upvotes: 0

Views: 1982

Answers (3)

Edwin Iskandar
Edwin Iskandar

Reputation: 4119

You should be able to group the fetched results by a transient property (e.g. itemState) that calculates the correct section the ToDoItem should appear in (i.e. 0=Overdue, 1=Today, 2=Tomorrow).

[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"itemState"
                                                   cacheName:@"Root"];

In your managed object, implement itemState to return the calculated state of the item:

- (NSString *)itemState {
    // logic to return @"Overdue", @"Today", etc.. based on whatever logic makes sense in your app
    // see this on how to implement transient properties http://davemeehan.com/technology/objective-c/core-data-transient-properties-on-nsmanagedobject

}

Then you can go ahead and use your FRC to implement your UITableViewControllerDataSource methods:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.fetchedResultsController sections].count;
}

... etc...

Upvotes: 1

Lorenzo B
Lorenzo B

Reputation: 33428

A nice way to achieve is to add a new attribute in your ToDoItem (e.g. state) that you can use as sectionNameKeyPath for your NSFetchedResultsController.

I'll try to explain with a simple example.

Think to a Food entity that lists foods. This entity has a name and a category. e.g.

Apple, Fruit
Banana, Fruit
Potato, Vegetable
Salad, Vegetable

If you use category as a sectionNameKeyPath you will find two sections. One will contain foods that belong to Fruit category, the other will contain the Vegetable one.

The same applies if use a state property in your entity (for the sake of simplicity it's a string but you could just use numbers and using a map to map each number to a string value). I guess this it's correct since each item will have a completion state. In addition, by means of this approach values associated with each state can change dynamically. And this should be part of the model and not of the controller. Hence, state could assume one of these possible values: @"Overdue", @"Today", @"Tomorrow", @"Upcoming", @"Someday", @"Completed".

As an important note (see Apple doc)

If the controller generates sections, the first sort descriptor in the array is used to group the objects into sections; its key must either be the same as sectionNameKeyPath or the relative ordering using its key must match that using sectionNameKeyPath.

Upvotes: 1

Caleb
Caleb

Reputation: 124997

I can think of at least two reasonable strategies. One is to do a number of separate fetches, one for each category. Each fetch request would be given a predicate that selects those ToDoItem objects whose due date and status are appropriate for each category. For example, the fetch request for "Today" would select items where the due date is the current date and the status is incomplete; the request for "Completed" would ignore the date and select all those items whose status is complete.

The other strategy is to fetch all the items at once and categorize them yourself after the fetch. This might be conceptually simpler, and you can still use NSPredicate to categorize the objects. For example, you can use NSSet's -objectsPassingTest: method or NSArray's -filteredArrayUsingPredicate: method.

Spend some time reading up on using predicates and fetching objects.

Upvotes: 1

Related Questions