如何为 UITableViewCell 显示自定义 UIMenuItem?

Posted

技术标签:

【中文标题】如何为 UITableViewCell 显示自定义 UIMenuItem?【英文标题】:How to show a custom UIMenuItem for a UITableViewCell? 【发布时间】:2012-09-05 22:47:14 【问题描述】:

我想要当我长按 UITableViewCell 以显示自定义 UIMenuItems 时弹出的 UIMenuController。

我在 viewDidLoad 中设置了自定义项

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

然后我设置了所有正确的委托方法。

- (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;

但它所做的只是显示“复制”项目,因为我只允许它和我的自定义项目。但是,自定义项目不会显示。

我意识到,我可以将手势识别器添加到单元格本身,但这违背了 UIMenuController 共享实例的目的,不是吗?

【问题讨论】:

【参考方案1】:

据我了解有两个主要问题:

1) 您希望 tableView canPerformAction: 支持自定义选择器,而文档说它只支持 UIResponderStandardEditActions 中的两个(复制和/或粘贴);

2) 不需要 || action == @selector(test:) 部分,因为您通过初始化 menuItems 属性来添加自定义菜单选项。对于此项目选择器,检查将是自动的。

要让自定义菜单项显示和工作,您可以做的是:

1) 修复表格视图委托方法

一)

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) 使用

设置单元格(子类化UITableViewCell
-(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 


///////////////////////////////////////////////////////

【讨论】:

哦,第二部分是解决方案。也感谢您用相关文档抚摸我的鼻子! 谢谢,它也帮助了我,但我做了一个改变:我在我的视图控制器上实现了我的自定义方法(test:)和响应者查询方法(canPerformAction:withSender:)在表视图子类中。 UIResponder 负责处理响应者链,因此如果仅用于此问题,则不需要子类。这让我很头疼——只要自定义方法有一个非常独特的名称来防止冲突。 ;-) 感谢@Randy Marsh 的评论,我只希望你的视图控制器不会长到数千行:) @A-live 我更喜欢将东西放在一个文件中并使用#pragma mark 来构造东西,而不是拥有许多类和文件。但是,是的,我很容易达到数千行。 ;-p 从 UITabBar 操作菜单进入此 UITableView 控制器时,我遇到了调用 canPerformAction 的问题。【参考方案2】:

为 UITableViewCell 实现复制和自定义操作:

一旦在您的应用程序中注册自定义操作:

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()

在您的 UITableViewCell 子类中,实现自定义方法:

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

在您的 UITableViewDelegate 中,实现以下方法:

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()
    

注意事项:

这使用新的Swift 3 #selectors。 有关how to implement copy 的信息,请参阅此答案。

【讨论】:

感谢这真的帮助了我。这在您长按单元格时有效,但是当我单击单元格时我们可以执行相同的操作吗?以及如何删除“复制”选项,它与我的自定义时间一起仍然存在。谢谢:) 请注意,使用 Xcode8/swift3 并将return action == "copy:" 提供给canPerformAction 会触发生成return action == #selector(UIResponderStandardEditActions.copy(_:)) 的修复程序【参考方案3】:

示例只允许复制第 0 节的第 0 行

更新到 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

【讨论】:

很高兴帮助你@XimenaFloresdelaTijera!【参考方案4】:

SWIFT 3:

在 AppDelegate 中 didFinishLaunchingWithOptions:

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

在您的 TableViewContoller 类中:

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
   

【讨论】:

您应该在应用程序的整个生命周期内只执行一次 UIMenuController 设置。否则,每次加载视图时,您都会获得额外的菜单项。 这就是为什么它在 viewDidLoad 中? 如果您要离开 UIViewController 的视图,它将被释放以节省内存。 viewDidLoad 将在下次加载视图时再次调用。如果 UIViewController 是根视图,你应该没问题,但如果你移动 UIViewController,你最终会得到重复的菜单项,以便后续抵抗 UIViewController。 viewDidLoad 可能在您的应用程序的生命周期内被多次调用,而您的 AppDelegate 的 applicationDidFinishLaunching: 将被调用一次,所以我认为这是设置菜单的更好地方。

以上是关于如何为 UITableViewCell 显示自定义 UIMenuItem?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 UITableViewCell 的子视图禁用 UITapGesture?

自定义 uitableviewcell 中的 Uibuttons

使用堆栈视图和自动布局创建自定义 UITableViewCell

如何为应用程序在后台显示的通知设置自定义布局?

如何为自定义帖子类型存档页面创建导航

如何为选择多行时使用的 UITableViewCell 图像蒙皮?