Saul Martínez
Saul Martínez

Reputation: 920

Adding a new row in a NSFetchedResultsController managed UITableView

I'm implementing a UITableView with NSFetchedResultsController.

# D5ProductViewController.h

@interface D5ProductViewController : D5ViewControllerAbstract <UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate>

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong) D5Product *product;
@property (weak, nonatomic) IBOutlet UITableView *tableView;

- (IBAction)addVariantTapped:(id)sender;

@end

# D5ProductViewController.m

@interface D5ProductViewController ()

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;

@end

@implementation D5ProductViewController

- (void)viewLoad {
    NSError *error;
    [self.fetcedResultsController performFetch:&error];
    if (error) {
        [self.alerts showError:[error localizedDescription]
                               :@"Error"];
    }
}

#pragma mark - Properties

@synthesize managedObjectContext; // The context is passed from a parent view.
@synthesize fetchedResultsController = _fetchedResultsController;

- (NSFetchedResultsController *)fetchedResultsController {
    if (_fetchedResultsController) {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Variant"
                                                         inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entityDescription];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"price"
                                                               ascending:NO];
    [fetchRequest setSortDescriptors:@[sortDescriptor]];

    NSString *predicateString = [NSString stringWithFormat:@"productId = '%@' AND isMaster = 0", self.product.identifier];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
    [fetchRequest setPredicate:predicate];
    [fetchRequest setReturnsObjectsAsFaults:NO];

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                managedObjectContext:self.managedObjectContext
                                                                  sectionNameKeyPath:nil
                                                                           cacheName:nil];
    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;
}

#pragma mark - NSFetchedResultsControllerDelegate

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

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

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:(D5VariantTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]
                 withObject:(D5Variant *)[controller objectAtIndexPath:indexPath]];
            break;
        case NSFetchedResultsChangeMove:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)configureCell:(D5VariantTableViewCell *)cell
           withObject:(D5Variant *)variant {
    cell.variant = variant;
}

#pragma mark - UITableViewDataSourceDelegate

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

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

#pragma mark - UITableViewDelegate

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    D5VariantTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:D5VariantCellReusableIdentifier
                                                                forIndexPath:indexPath];
    D5Variant *variant = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.variant = variant;
    cell.managedObjectContext = self.managedObjectContext;

    return cell;
}

- (IBAction)addVariantTapped:(id)sender {
    NSEntityDescription *variantEntityDescription = [NSEntityDescription entityForName:@"Variant"
                                                            inManagedObjectContext:self.managedObjectContext];
    D5Variant *variant = [[D5Variant alloc] initWithEntity:variantEntityDescription
                        insertIntoManagedObjectContext:self.managedObjectContext];

    variant.productId = self.product.identifier;
    variant.price = [self.product.master price];
    variant.weight = [self.product.master weight];

    [variant markAsInserted];

    NSError *error;
    [self.fetchedResultsController performFetch:&error];
    if (error) {
        NSLog(@"%@", error);
    }
    [self.tableView reloadData];
}

@end

If I understand correctly, having implemented NSFetchedResultsController's controller:didChangeObject:atIndexPath:forChangeType:newIndexPath will cause UITableView to ask for a cell for the inserted row, which is configured with the object at the given indexPath from the fetchedResultsController.

The thing is that controller:didChangeObject...newIndexPath is never called when a new object is inserted in the managedObjectContext.

What am I missing? Do I need to call managedObjectContext's save method and then [self.tableView reloadData].

Thanks in advance!

Upvotes: 3

Views: 684

Answers (1)

Saul Mart&#237;nez
Saul Mart&#237;nez

Reputation: 920

Silly me, I forgot to set the UITableView's delegate:

The viewDidLoad method, besides setting the bindings, was just performing the fetch. Adding self.tableView.delegate = self; fixed the problem, BUT, isn't it enough setting the dataSource outlet in the IB? Which I already did. Why did I need to set the UITableView's delegate manually? In the case of the NSFetchedResultsController it needs to be set manually, but isn't it the purpose of the UITableView object having that outlet to just wire it up to the delegate object?

Anyways, setting self.tableView.delegate = self; fixed the problem.

Upvotes: 1

Related Questions