First time that I'm using UICollectionView, and I'm having some difficulties. Especially with updating and deleting cells (will focus on delete here, hopefully the came cause), with data from a NSFetchResultController
. I have made a custom cell in in interface builder as part of a storyboard like this:
I have a custom UICollectionViewCell
subclass with the following properties:
@property (strong, nonatomic) IBOutlet UIButton *deleteButton;
@property (strong, nonatomic) IBOutlet UITextField *textField;
@property (strong, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) IBOutlet UIView *textFieldContainer;
In IB I have set the cell class to my custom class, connected the elements to the properties of my custom class and set the identifier to Cell
.
In my Collection View view controller I set up the collection view and fetchResultController and relevant methods like this:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NoteCollectionViewCell* cell = (NoteCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(NoteCollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.deleteButton.tag = indexPath.row;
[cell.deleteButton addTarget:self action:@selector(deleteNote:) forControlEvents:UIControlEventTouchUpInside];
[...]
// I'm having some weird problem with this, se description below.
Note *note = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textField.tag = indexPath.row;
cell.textField.delegate = self;
cell.textField.text = note.name;
[...]
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
[Default FRC method]
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UICollectionView *collectionView = self.collectionView;
switch(type) {
case NSFetchedResultsChangeInsert:
[collectionView insertItemsAtIndexPaths:@[newIndexPath]];
break;
case NSFetchedResultsChangeDelete:
[collectionView deleteItemsAtIndexPaths:@[indexPath]];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(NoteCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath] atIndexPath:indexPath];
break;
}
}
My delete action looks like this:
- (void)deleteNote:(UIButton *)sender
{
Note *note = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:sender.tag inSection:0]];
[[AppDelegate sharedAppDelegate].managedObjectContext deleteObject:note];
[[AppDelegate sharedAppDelegate] saveContext];
}
My update action (UITextField
delegate method) looks like this:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
Note *note = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:textField.tag inSection:0]];
note.name = textField.text;
[[AppDelegate sharedAppDelegate] saveContext];
}
The problem(s) is as follows:
- The delete button don't always delete the right cells/object. And sometime the object/cell won't be deleted at all.
- Sometime the app crashes when I delete an object (SIGARBT error).
- Sometimes the text shows up in the wrong textfield.
- Sometimes when I delete row 0 with some text, then add press add, a new cell is added with the same text value as the previous (deleted) cell.
Any ideas on how to solve these problems would be great!
Update
As comment below, this log in my deleteNote
action always returns 0 for indexPath row. Don't know if that could be the cause.
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:sender.tag inSection:0];
NSLog(@"indexPath: %@. Row: %d", indexPath, indexPath.row);
You can't link a fetched results controller to a collection view in quite the same way as a table view. A fetched results controller was designed specifically to mediate between core data and a table view, so the delegate methods tie in with how a table view works.
The main difference is that a table view has a
beginUpdates
/endUpdates
method pair that you can wrap all of the updates in. The collection view doesn't have this, you have to instead build up all of the required update calls yourself, then when the fetched results controller has finished updating, execute them all within aperformBatchUpdates:completion:
call.There is an example implementation of this on GitHub.
I don't know if that explains your problem, but you call
each time in
configureCell:atIndexPath:
, so that multiple actions are attached to the button if a cell is modified or reused. Then thedeleteNote:
would be called more than once for one touch event.As a workaround, you could check
cell.deleteButton.tag
to see if an action has already been attached to the button, or better:NoteCollectionViewCell
class to a local action in that class when the cell is created,