Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show a custom UIMenuItem for a UITableViewCell?

I want the UIMenuController that pops up when I long-press a UITableViewCell to show custom UIMenuItems.

I set up the custom item in viewDidLoad

UIMenuItem *testMenuItem = [[UIMenuItem alloc] initWithTitle:@"Test" action:@selector(test:)];
[[UIMenuController sharedMenuController] setMenuItems: @[testMenuItem]];

And then I set all the right delegate methods.

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

-(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return (action == @selector(copy:) || action == @selector(test:));
}

- (BOOL)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    if (action == @selector(copy:)) {
         // do stuff
    }

    return YES;
}

But all it does, is show the "Copy" item, since I only allow it and my custom item. The custom item, however, won't show up.

I realize, I could add a gesture recognizer to the cell itself, but that kind of defeats the purpose of the shared instance of UIMenuController, doesn't it?

like image 691
leberwurstsaft Avatar asked Sep 05 '12 22:09

leberwurstsaft


4 Answers

As far as I understand there are two main problems:

1) you expect tableView canPerformAction: to support custom selectors while the documentation says it supports only two of UIResponderStandardEditActions (copy and/or paste);

2) there's no need for the part || action == @selector(test:) as you are adding the custom menu options by initializing menuItems property. For this items selectors the check will be automatical.

What you can do to get the custom menu item displayed and work is:

1) Fix the table view delegate methods with

a)

UIMenuItem *testMenuItem = [[UIMenuItem alloc] initWithTitle:@"Test" action:@selector(test:)]; [[UIMenuController sharedMenuController] setMenuItems: @[testMenuItem]]; [[UIMenuController sharedMenuController] update]; 

b)

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {     return YES; }  -(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {     return (action == @selector(copy:)); }  - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {     // required } 

2) Setup the cells (subclassing UITableViewCell) with

-(BOOL) canPerformAction:(SEL)action withSender:(id)sender {     return (action == @selector(copy:) || action == @selector(test:)); }  -(BOOL)canBecomeFirstResponder {     return YES; }  /// this methods will be called for the cell menu items -(void) test: (id) sender {  }  -(void) copy:(id)sender {  } /////////////////////////////////////////////////////// 
like image 101
A-Live Avatar answered Nov 16 '22 03:11

A-Live


To implement copy and a custom action for UITableViewCell:

Once in your application, register the custom action:

struct Token { static var token: dispatch_once_t = 0 } dispatch_once(&Token.token) {     let customMenuItem = UIMenuItem(title: "Custom", action: #selector(MyCell.customMenuItemTapped(_:))     UIMenuController.sharedMenuController().menuItems = [customMenuItem]     UIMenuController.sharedMenuController().update() } 

In your UITableViewCell subclass, implement the custom method:

func customMenuItemTapped(sender: UIMenuController) {     // implement custom action here } 

In your UITableViewDelegate, implement the following methods:

override func tableView(tableView: UITableView, shouldShowMenuForRowAtIndexPath indexPath: NSIndexPath) -> Bool {     return true }  override func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool {     return action == #selector(NSObject.copy(_:)) || action == #selector(MyCell.customMenuItemTapped(_:)) }  override func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) {     switch action {     case #selector(NSObject.copy(_:)):         // implement copy here     default:         assertionFailure()     } } 

Notes:

  • This uses the new Swift 3 #selectors.
  • See this answer for info on how to implement copy.
like image 20
Senseful Avatar answered Nov 16 '22 05:11

Senseful


Example allowing copy only row 0 of section 0

Updated to Swift 5.2

func shouldAllowCopyOn(indexPath: IndexPath) -> Bool {
    if indexPath.section == 0 && indexPath.row == 0 {
       return true
    }
    return false
}

func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
    return self.shouldAllowCopyOn(indexPath: indexPath)
}

func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
    if (action == #selector(UIResponderStandardEditActions.copy(_:))) {
          return self.shouldAllowCopyOn(indexPath: indexPath)
    }
}

func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
    if (action == #selector(UIResponderStandardEditActions.copy(_:)) && self.shouldAllowCopyOn(indexPath: indexPath)) {
       if let cell = self.tableView.cellForRow(at: indexPath) as? UITableViewCell {
         self.copyAction(cell: cell)
        }
    }
}

@objc
private func copyAction(cell: UITableViewCell) {
    UIPasteboard.general.string = cell.titleLabel.text
}
like image 23
Reinier Melian Avatar answered Nov 16 '22 04:11

Reinier Melian


SWIFT 3:

In AppDelegate didFinishLaunchingWithOptions:

let customMenuItem = UIMenuItem(title: "Delete", action:
#selector(TableViewCell.deleteMessageActionTapped(sender:)))
        UIMenuController.shared.menuItems = [customMenuItem]
        UIMenuController.shared.update()

in your TableViewContoller Class:

override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
    return true
}

override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
        return action == #selector(copy(_:)) || action == #selector(TableViewCell.yourActionTapped(sender:))
    }



 override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
   if action == #selector(copy(_:)) {
        let pasteboard = UIPasteboard.general
        pasteboard.string = messages[indexPath.row].text
   }
}
like image 43
Yaroslav Dukal Avatar answered Nov 16 '22 03:11

Yaroslav Dukal