如何为 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