8

I have a simple UICollectionView based app - one UICollectionView and a NSMutableArray based data model for simplicity.

I can delete cells with no problem via the didSelectItemAtIndexPath: delegate method:

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    [self.data removeObjectAtIndex:[indexPath row]];
    [self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
}

However, I'm trying to add a delete option via a UIMenuController in a UICollectionViewCell subclass which is triggered via a UILongPressGestureRecognizer which all works fine and I successfully trigger an NSNotification

-(void)delete:(id)sender{
      NSLog(@"Sending deleteme message");
      [[NSNotificationCenter defaultCenter] postNotificationName:@"DeleteMe!" object:self userInfo:nil];
}

I catch it in my ViewController and call the following method:

-(void)deleteCell:(NSNotification*)note{
       MyCollectionViewCell *cell = [note object];
       NSIndexPath *path = nil;
       if((path = [self.collectionView indexPathForCell:cell]) != nil){
           [self.data removeObjectAtIndex:[path row]];
           [self.collectionView deleteItemsAtIndexPaths:@[path]];
       }
}

And it crashes on the deleteItemsAtIndexPaths: call

-[UICollectionViewUpdateItem action]: unrecognized selector sent to instance 0xee7eb10

I've checked everything obvious - like the object from NSNotification and the indexPath created from the indexPathForCell: call and it all seems totally fine. It seems like I'm calling deleteItemsAtIndexPath: with the same information in both places, but for some reason it fails when it goes via the notification route.

This is the info at the address given in the error:

(lldb) po 0xee7eb10
(int) $1 = 250080016 <UICollectionViewUpdateItem: 0xee7eb10> index path before update (<NSIndexPath 0x9283a20> 2 indexes [0, 0]) index path after update ((null)) action (delete)

Perhaps the index path after update being null is significant...

Any ideas?

melps
  • 1,247
  • 8
  • 13
  • In `deleteCell:` you use `self.collectionViewOne` and `self.collectionView` - is that on purpose? – Martin R Oct 15 '12 at 11:49
  • I can confirm this also happens when inserting new items from a notification. – Brett Nov 20 '12 at 11:21
  • 1
    I got the same problem, I'm afraid it's caused by the UIMenuController! Bks when I embed a UITextView in a UICollectionViewCell, if long press the UITextView to show the system default menu, then got the warning:"[UICollectionViewUpdateItem action]: unrecognized selector sent to instance", if insert an item, same error as yours. – ZYiOS Nov 22 '12 at 12:47
  • 1
    Ok, so the cause seems to be UITextView itself inside a UICollectionViewCell content view. Don't know if its related but the first warning when creating a cell with a UITextView is: 'setting the first responder view of the collection view but we don't know its type (cell/header/footer)'. Then subsequent attempts to insert/delete items in the collection view results in the crash. In my case it was just a coincidence that this happened inside a notification block. @melps does your MyCollectionViewCell class contain a UITextView? – Brett Nov 22 '12 at 16:42
  • 1
    Think I've pinpointed the issue and in my case the exception occurs when trying to insert/delete items in a collection view when the keyboard is showing. Dismissing the keyboard first before inserting/deleting seems to resolve the issue – Brett Nov 23 '12 at 09:59
  • Keyboard was the issue with me also. – Michael May 09 '13 at 07:26

3 Answers3

25

I found a crude but working workaround, and it even checks if action is already implemented in a future release (better than a category)

// Fixes the missing action method when the keyboard is visible
#import <objc/runtime.h>
#import <objc/message.h>
__attribute__((constructor)) static void PSPDFFixCollectionViewUpdateItemWhenKeyboardIsDisplayed(void) {
    @autoreleasepool {
    if ([UICollectionViewUpdateItem class] == nil) return; // pre-iOS6.
    if (![UICollectionViewUpdateItem instancesRespondToSelector:@selector(action)]) {
            IMP updateIMP = imp_implementationWithBlock(^(id _self) {});
            Method method = class_getInstanceMethod([UICollectionViewUpdateItem class], @selector(action));
            const char *encoding = method_getTypeEncoding(method);
            if (!class_addMethod([UICollectionViewUpdateItem class], @selector(action), updateIMP, encoding)) {
                NSLog(@"Failed to add action: workaround");
            }
        }
    }
}

Edit: Added check for iOS5.
Edit2: We're shipping that in lots of commercial projects (http://pspdfkit.com) and it works great.

steipete
  • 7,581
  • 5
  • 47
  • 81
  • 1
    This answer worked like a charm for me. Should be marked as the solution. – Michael Thiel Jan 18 '13 at 17:03
  • thxs, worked for me. BTW, i was getting then when setting a view inside a cell as a first responder, and there is no keyboard visible – deeje cooley May 17 '13 at 19:19
  • Does someone have a minimal example of what causes this? I'm experiencing a crash in another app without keyboard being displayed or changing the first responder. – Joshua Gross Aug 28 '13 at 18:03
  • This really seems to work. Using this in a test version and the crash has disappeared. However there seems to be some weird stuff with cells that is "still present" but outside of the scrolling area. It's just as if they wasn't deleted at all when the crash should have appeared. However, I can live with it. – ullstrm Aug 26 '14 at 11:55
0

You could pass in the NSIndexPath of the selected cell in the userInfo dictionary of your notification. You could set that in the tag of your custom cell when it is created.

swilliams
  • 48,060
  • 27
  • 100
  • 130
  • But it's not an issue of the NSIndexPath being wrong, so I'm not sure what that would help. – melps Nov 11 '12 at 13:50
0

I had the same issue. My app would crash when I tried to insert a cell into my UICollectionView after I had selected text in a UITextField which was placed inside a collectionViewCell. My fix was to resign the first responder before the insertion happened. Since I was using a NSFetchedResultsController to handle the updating I put this at the beginning of controllerWillChangeContent:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [[UIApplication sharedApplication].keyWindow findAndResignFirstResponder];
    ...
}

I found findAndResignFirstResponder from this SO answer

Community
  • 1
  • 1
moshe
  • 157
  • 10