5

So, I am not sure if I am doing something wrong here, but I have a UIViewController that has a UICollectionView on it. In the UIViewController's viewDidLoad method, I do the following it doesn't add any custom menu items to the popup that shows up.

UIMenuItem *removeItem = [[UIMenuItem alloc] initWithTitle:@"Remove" action:@selector(handleRemoveItem:)];
UIMenuItem *duplicateItem = [[UIMenuItem alloc] initWithTitle:@"Duplicate" action:@selector(handleDuplicateItem:)];

[[UIMenuController sharedMenuController] setMenuItems:@[removeItem, duplicateItem]];

[removeItem release];
[duplicateItem release];

I did set the collectionView:shouldShowMenuForItemAtIndexPath: and the collectionView:canPerformAction:forItemAtIndexPath:withSender: to return YES under all circumstances, but no matter what, only Cut, Copy, and Paste will show up.

Did I not implement this fully, or did I not do it right?

P.S. - I did look at as many examples as I could throughout google and I didn't find anything that helped.

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Andrew Riebe
  • 411
  • 7
  • 17

3 Answers3

7

I was able to implement custom menus on a UICollectionViewCell by following the instructions on this link (https://stackoverflow.com/a/13618212/2313416) with some improvising.

In my UICollectionViewController, I implemented the custom menu items by adding them to the menu controller as in the link.

I then implemented the following in the UICollectionViewController:

- (BOOL)collectionView:(UICollectionView *)cv canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
return NO;
}

- (BOOL)collectionView:(UICollectionView *)cv shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}

- (void)collectionView:(UICollectionView *)cv performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {

NSLog(@"perform action:%@", NSStringFromSelector(action));
}

In my UICollectionViewCell, I implemented similar to the following:

- (BOOL)canBecomeFirstResponder {
return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {

if (action == @selector(onCustom1:)) {
    return YES;
}

if (action == @selector(onCustom2:)) {
    return YES;
} 
return NO;
}

These actions have to be the same as implemented in the Collection Controller.

If one wants to include the copy or paste functions, add them to the canPerformAction: and then change the collectionView::canPerformAction: to return YES.

This may not be the best way of doing it, but has worked for me.

Community
  • 1
  • 1
Jan-Michael
  • 71
  • 1
  • 3
  • This is causing a crash since it can't find the `onCustom1:` how did you route it to the collectionView? – Ryan Poolos May 07 '13 at 15:34
  • @RyanPoolos In the CollectionViewController viewDidLoad, I created a NSArray of custom UIMenuItems objects. For their actions, I put '@action(onCustom1:)' and '@action(onCustom2:)'. In the UICollectionViewCell header file, I declared '-(void)onCustom1:(id)sender' and '-(void)onCustom2:(id)sender'. The methods were then put into my UICollectionViewCell.m file. – Jan-Michael May 08 '13 at 02:57
  • Oh ok. I thought you had found a way to somehow bridge between the cell and the collection. – Ryan Poolos May 08 '13 at 11:46
  • adding that to the collectionView cell worked like a charm. This worked before in ios6 http://paulsolt.com/2012/11/uicollectionview-custom-actions-and-uimenucontroller/ but stopped for ios7 so with your tip i got my app working again! – DogCoffee Sep 13 '13 at 09:42
4

You are correct. It is impossible to customize the menu that appears when you do a long press on a table view cell or collection view cell.

I discuss the problem in my book:

http://www.apeth.com/iOSBook/ch21.html#_table_view_menus

As I say there, Copy, Cut and Paste are the only choices you can have. You will have to make the menu emanate from something else if you want to customize it.

EDIT: In the iOS 7 version of my book, I demonstrate a way to do this. It's the same for table view cells and collection view cells, so I'll start with the table view cell solution. The trick is that you must implement the action method in a cell subclass. For example, if your custom action selector is abbrev:, you must subclass the cell and implement abbrev::

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch08p454tableCellMenus2/ch21p718sections/MyCell.m

That's the only tricky part. Then, back in your controller class, you do for abbrev: exactly what you would do for any menu. In shouldShowMenuForRowAtIndexPath:, add it to the custom menu. Then implement canPerformAction: and performAction: just as you would expect (scroll all the way to the bottom):

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch08p454tableCellMenus2/ch21p718sections/RootViewController.m

Here's the parallel implementation for collection view cells: the cell subclass:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch08p466collectionViewFlowLayout2/ch21p748collectionViewFlowLayout2/Cell.m

And the controller (scroll all the way to the bottom):

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/iOS7bookExamples/bk2ch08p466collectionViewFlowLayout2/ch21p748collectionViewFlowLayout2/ViewController.m

Those approaches are also translated into Swift (not without some difficulty) in the iOS 8 edition of my book.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Yeah, I kind of got that feeling, so I changed how I am using it to display a `UIActionSheet` for the time being. Once I get a chance to make my own collection view, then I will probably get that implemented. Thanks! – Andrew Riebe Apr 03 '13 at 16:14
  • Also pls file a bug with Apple. This is a really silly limitation. – matt Apr 03 '13 at 16:18
  • it works if you do it this way, http://paulsolt.com/2012/11/uicollectionview-custom-actions-and-uimenucontroller/ well it did in ios6 – DogCoffee Sep 13 '13 at 09:38
  • Well, i think you can do. Here is a way: `UIMenuItem *duplicateMenuItem = [[UIMenuItem alloc] initWithTitle:@"duplicate"] action:@selector(duplicate:)]; _menuController.menuItems = [NSArray arrayWithObjects:duplicateMenuItem,nil];` And ofcource, you have to define the selector: `- (void)duplicate:(id)sender { // do your stuff; }` – Sourabh Bhardwaj Dec 18 '14 at 12:15
  • @Sourabh I did ultimately find a way to do it. I'll modify my answer to describe it. – matt Dec 18 '14 at 14:56
1

Step 1 : Create Menu Items

UIMenuItem* miCustom1 = [[UIMenuItem alloc] initWithTitle:@"Custom 1" action:@selector(onCustom1:)];
UIMenuItem* miCustom2 = [[UIMenuItem alloc] initWithTitle: @"Custom 2" action:@selector(onCustom2:)];

Step 2: Create MenuController

UIMenuController* mc = [UIMenuController sharedMenuController];

Step 3 : Add Items to Menu Controller

mc.menuItems = [NSArray arrayWithObjects: miCustom1, miCustom2, nil];

Step 4 : Create Action Methods for Items

- (void) onCustom1: (UIMenuController*) sender
{
}

- (void) onCustom2: (UIMenuController*) sender
{
}

Step 5 : its optionally to set FirstResponder for Actions

- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
    if ( action == @selector( onCustom1: ) )
    {
        return YES; // logic here for context menu show/hide
    }

    if ( action == @selector( onCustom2: ) )
    {
        return NO;  // logic here for context menu show/hide
    }

    if ( action == @selector( copy: ) )
    {
        // turn off copy: if you like:
        return NO;
    }

    return [super canPerformAction: action withSender: sender];
}

Step 6 : Finally Show your MenuController on Some Button Action

UIMenuController* mc = [UIMenuController sharedMenuController];

CGRect bounds = sender.view.bounds;

[mc setTargetRect: sender.view.frame inView:sender.view.superview];
[mc setMenuVisible:YES animated: YES];
Dipen Panchasara
  • 13,480
  • 5
  • 47
  • 57