KVO controller for NSSet that behaves like an NSFetchedResultController without Core Data.
Objective-C Ruby Shell C
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
CMSetController
Project
.gitignore
CHANGELOG.md
CMSetController.podspec
LICENSE
README.md

README.md

CMSetController

CMSetController is a KVO controller for NSSet that behaves like an NSFetchedResultController without Core Data.

Given an object with an NSSet property containing objects you want to bind to a table view, provide (very similar to an NSFetchedResultController):

  • The key path of the set.
  • One or more key paths of properties to observe for the objects in the set.
  • One or more sort descriptors to order the objects.
  • Optionally the key path to a property of the objects to group them by.

The controller will provide a set of sections and objects to populate a UITableView datasource. It will also call back a delegate class when changes occur to the objects in the set for update, insertion, removal, reordering, and section changes.

Usage

Configure the CMSetController when configuring the view for the bound item. If you don't have a model object that holds a set, make the set a property of your controller.

// watch for changes to either the "name" or "favoriteColor" properties
NSArray *paths = @[@"name", @"favoriteColor"];

// sort by "favoriteColor" (first sort must match section key path, if any) then "name"
NSArray *sorts = @[[NSSortDescriptor sortDescriptorWithKey:@"favoriteColor" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];

// observe the "friends" set property of the "self.document" object using the key paths and
// sorts setup above, group by "favoriteColor" into sections, and receive delegate callbacks
// as the set changes
_setController = [[CMSetController alloc] initWithObserved:self.document
                                                setKeyPath:@"friends"
                                                  keyPaths:paths
                                        sectionNameKeyPath:@"favoriteColor"
                                           sortDescriptors:sorts
                                                  delegate:self];
        
NSError *error;
if (![_setController performQuery:&error]) {
    NSLog(@"oops - %@", error);
    abort();
}
        
[self.tableView reloadData];

Populate the table view just like using an NSFetchedResultController:

#pragma mark - Table view

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    CMFriend *friend = [[[[_setController sections] objectAtIndex:indexPath.section] objects] objectAtIndex:indexPath.row];
    
    cell.textLabel.text = friend.name;
    cell.detailTextLabel.text = friend.favoriteColor;
}

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

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [[[_setController sections] objectAtIndex:section] name];
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    [self configureCell:cell atIndexPath:indexPath];
    
    return cell;
}

If you've implemented the CMSetControllerDelegate, you will get callbacks tailored to update the table view:

#pragma mark - Query Delegate

- (void)controllerWillChangeContent:(CMSetController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(CMSetController *)controller didChangeSection:(id<CMSetControllerSectionInfo>)sectionInfo atIndex:(NSUInteger)index forChangeType:(CMSetControllerChangeType)type
{
    NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
    
    if (type == CMSetControllerChangeInsert)
        [self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
    else
        [self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
}

- (void)controller:(CMSetController *)controller didChangeObject:(id)object atIndexPath:(NSIndexPath *)indexPath forChangeType:(CMSetControllerChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    switch (type) {
        case CMSetControllerChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath]
                                  withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case CMSetControllerChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath]
                                  withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
            case CMSetControllerChangeUpdate:
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            
        case CMSetControllerChangeMove:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath]
                                  withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath]
                                  withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        default:
            break;
    }
}

- (void)controllerDidChangeContent:(CMSetController *)controller
{
    [self.tableView endUpdates];
}

Contact

Samuel Ford @causticmango

License

CMSetController is released under the MIT license. See LICENSE file for more details.