Is there a simple way for subclasses of UITableViewCell to show the \'Copy\' UIMenuController popup like in the Address book app (see screenshot), after the selection is hel
Here is the Swift syntax for copying detailTextLabel
.
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return (tableView.cellForRow(at: indexPath)?.detailTextLabel?.text) != nil
}
func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
let cell = tableView.cellForRow(at: indexPath)
let pasteboard = UIPasteboard.general
pasteboard.string = cell?.detailTextLabel?.text
}
}
#pragma mark - COPY/PASTE Cell Text via Menu
- (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
{
if (action == @selector(copy:))
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
[pasteBoard setString:cell.textLabel.text];
}
}
Your UITableViewCell subclass may look like this
@interface MenuTableViewCell : UITableViewCell {
}
- (IBAction)copy:(id)sender;
- (void)showMenu;
@end
@implementation MenuTableViewCell
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == @selector(copy:)) {
return YES;
}
return NO;
}
- (IBAction)copy:(id)sender {
}
- (void)showMenu {
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
[self becomeFirstResponder];
[[UIMenuController sharedMenuController] update];
[[UIMenuController sharedMenuController] setTargetRect:CGRectZero inView:self];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
}
@end
And the UITableView delegate methods are like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
MenuTableViewCell *cell = (MenuTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[MenuTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MenuTableViewCell *cell = (MenuTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
[cell showMenu];
}
created 2 scenarios out of Alexander's Code:
1.in case you want to copy textLabel and not detailTextLabel just use this code:
//MARK: Delegate
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return (tableView.cellForRow(at: indexPath)?.textLabel?.text) != nil
}
func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
let cell = tableView.cellForRow(at: indexPath)
let pasteboard = UIPasteboard.general
pasteboard.string = cell?.textLabel?.text
}
}
2.if you have customCell with customLabels and you want to copy all customLabels text Do this:
//MARK: Delegate
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return (tableView.cellForRow(at: indexPath) != nil)
}
func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
/* change these array names according to your own array names */
let customlabel1 = customlabel1Array[indexPath.row]
let customlabel2 = customlabel2Array[indexPath.row]
let customlabel3 = customlabel3Array[indexPath.row]
let pasteboard = UIPasteboard.general
pasteboard.string = "\(customlabel1)\n\(customlabel2)\n\(customlabel3)" /* \n is for new line. */
}
}
}
By the way you should set your tableView delegate to self in viewDidLoad for these to work, like this:
override func viewDidLoad() {
yourTableView.delegate = self
}
For pre iOS13, refer to Alexander's answer. For iOS13 shouldShowMenuForRowAt
and canPerformAction
are deprecated, hence you have to do use the following API:
@available(iOS 13.0, *)
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in
return self.makeContextMenu(for: indexPath)
})
}
@available(iOS 13.0, *)
func makeContextMenu(for indexPath: IndexPath) -> UIMenu {
let copyAction = UIAction(title: "Copy") { [weak self] _ in
guard let self = self else { return }
let cell = self.tableView.cellForRow(at: indexPath)
let pasteboard = UIPasteboard.general
pasteboard.string = cell?.detailTextLabel?.text
}
// Create and return a UIMenu with the share action
return UIMenu(title: "Options", children: [copyAction])
}
Note: The end result will be different. But this is out of the box functionality that Apple is providing. However checking the Settings app on iOS 13 Sim >> General >> About. long-pressing a cell will not have the UI below, it will be the old UI, which is a bit inconsistent.
There is now official interface for displaying UITableView cell menus in iOS 5. Example (from the table delegate):
- (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
{
if (action == @selector(copy:)){
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[[UIPasteboard generalPasteboard] setString:cell.textLabel.text];
}
}
I tried modifying the UIMenuController's shared controller to add my own menu item, and I was able to add it and get the canPerformAction
message for it, but returning YES didn't help; I wasn't able to make my custom menu item appear. From my experiments, it looks like only Copy, Cut, and Paste are supported. [EDIT Since this was posted, I've learned how to add custom menu items.]
Note that this works only if all three delegate methods are implemented.