Alex Pretzlav's Tumbleblag
4 notes
Adding Text Copy Support to a Grouped UITableView for the iPhone
One of the things I’ve wanted to add to the Yelp iPhone App, pretty much since I started in July, is copy and paste support for business info pages. This would allow people to text a business’ phone number to a friend (since there’s no API for sending a message body to the Messages app), or copy an address to paste into the search location field.

The built in Address Book app has an excellent copy and paste model: press and hold on a grouped table view cell to bring up the Copy menu. Tap a field quickly to activate its functionality (e.g. call a phone number or open a map). A few weeks ago I finally set out to bring this functionality to our business pages.
It turns out this was way harder than I expected. There are a number of subtle things Address Book does to make copying a table cell’s contents feel smooth and natural, and Apple doesn’t make it easy to emulate this functionality. In particular, Apple’s copy and paste functionality
- Prevents the table view from scrolling once the Copy menu appears, until you touch elsewhere on the screen
- Keeps the current cell highlighted until you tap elsewhere on the screen
- Allows you to directly tap another cell to activate its functionality or tap and hold on another cell to copy its contents
If you want to cut to the chase, I’ve put up a code example at github where you can browse the source, or can you just grab a tarball and see for yourself.
There were several aspects of UIKit that made this difficult to implement. Firstly, there is no built-in support in UITableViewDelegate to tell when a cell is highlighted (i.e. a user has pressed and held on a cell); only when it is selected (when the user lifts her finger from the cell). To tell when this has happened and show the Edit menu after a delay, I had to subclass UITableViewCell and add a delegate method to alert my controller that it had become highlighted.
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
if (delegate)
[delegate copyableTableViewCell:self willHighlight:highlighted];
[super setHighlighted:highlighted animated:animated];
}
To handle the actual edit menu display, I use performSelector:withObject:afterDelay: and check to make sure the highlighted cell is still highlighted:
- (void)copyableTableViewCell:(CopyableTableViewCell *)copyableTableViewCell willHighlight:(BOOL)highlighted {
if (highlighted)
[self performSelector:@selector(showSelectMenuForCell:) withObject:copyableTableViewCell afterDelay:0.3];
}
- (void)showSelectMenuForCell:(CopyableTableViewCell *)cell {
// If cell is still highlighed from when this method was called 0.3 seconds ago, show Copy menu
if ([cell isHighlighted]) {
UIMenuController *sharedMenu = [UIMenuController sharedMenuController];
[cell becomeFirstResponder];
[sharedMenu setTargetRect:cell.frame inView:self.view];
[sharedMenu setMenuVisible:YES animated:YES];
// Select cell so it doesn't become un-highlighted if finger moves off it while showing edit menu
[self.tableView selectRowAtIndexPath:[self.tableView indexPathForCell:cell] animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
The only odd thing happening here is selecting the cell when the edit menu appears. This is because if you don’t directly select the cell, then if the user moves his finger off the cell (while keeping it on the screen) the cell will un-highlight if it’s not selected. You’ll see in the next code fragment that I have to manually deselect the cell when the select menu is no longer visible.
The last difficult part is understanding exactly how and when the UIMenuController appears, so I can make sure the cell stays highlighted and the table view can’t be scrolled until just the right set of things has happened. This involves listening for UIMenuControllerWillHideMenuNotification, UIMenuControllerWillShowMenuNotification, and UIMenuControllerDidShowMenuNotification.
It turns out that once you lock a UIScrollView, it won’t scroll until the user lifts her finger from the screen, so (surprisingly) this simple implementation worked for the scroll locking:
- (void)menuControllerWillHide:(NSNotification *)notification {
_showingEditMenu = NO;
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
- (void)menuControllerWillShow:(NSNotification *)notification {
_showingEditMenu = YES;
self.tableView.scrollEnabled = NO;
}
- (void)menuControllerDidShow:(NSNotification *)notification {
self.tableView.scrollEnabled = YES;
}
I use _showingEditMenu to know when not to invoke the cell’s normal tap functionality.
Now all you have to do to get copy and paste working is subclass UITableViewCell to report the correct information for the UIResponderStandardEditActions protocol and implement canPerformAction:withSender:. This was very straightforward and well documented by Apple; you can look at my example at github if you’re curious.
I would love to find a simpler way to get this behavior, so if you’ve got any ideas or recommendations, please let me know in the comments!
-
jessearmand reblogged this from apretz and added:
trouble myself in detecting...selection/highlight of UITableViewCell.
-
digdog liked this
-
do-nothing reblogged this from apretz
-
apretz posted this