NSFetchResultController with UICollectionView issu

2019-07-13 19:01发布

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:

enter image description here

I have a custom UICollectionViewCellsubclass 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);

2条回答
在下西门庆
2楼-- · 2019-07-13 19:52

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 a performBatchUpdates:completion: call.

There is an example implementation of this on GitHub.

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-07-13 19:55

I don't know if that explains your problem, but you call

[cell.deleteButton addTarget:self action:@selector(deleteNote:)  forControlEvents:UIControlEventTouchUpInside];

each time in configureCell:atIndexPath:, so that multiple actions are attached to the button if a cell is modified or reused. Then the deleteNote: 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:

  • add the action in your NoteCollectionViewCell class to a local action in that class when the cell is created,
  • use delegation to forward the action to the collection view controller.
查看更多
登录 后发表回答