UIMenuController
Mobile usability today is truly quite remarkable—especially considering how far it’s come in just the last decade. What was once a clumsy technology relegated to the tech elite has now become the primary mode of computation for a significant portion of the general population.
Yet despite its advances, one can’t help but feel occasionally… trapped.
All too often, there will be information on the screen that you just can’t access. Whether its flight information stuck in a table view cell or an unlinked URL, users are forced to solve problems creatively for lack of a provided solution.
In the past, we’ve mentioned localization and accessibility as two factors that distinguish great apps from the rest of the pack. This week, we’ll add another item to that list: Edit Actions.
Copy, Cut, Paste, Delete, Select
iOS 3’s killer feature was undoubtedly push notifications, but the ability to copy-paste is probably a close second. For how much we use it everyday, it’s difficult to imagine how we got along without it. And yet, it remains a relatively obscure feature for 3rd-party apps.
This may be due to how cumbersome it is to implement. Let’s look at a simple implementation, and then dive into some specifics about the APIs. First the label itself:
class Hipster Label : UILabel {
override func can Become First Responder() -> Bool {
return true
}
override func can Perform Action(action: Selector, with Sender sender: Any Object?) -> Bool {
return (action == "copy:")
}
// MARK: - UIResponder Standard Edit Actions
override func copy(sender: Any Object?) {
UIPasteboard.general Pasteboard().string = text
}
}
// Hipster Label.h
@interface Hipster Label : UILabel
@end
// Hipster Label.m
@implementation Hipster Label
- (BOOL)can Become First Responder {
return YES;
}
- (BOOL)can Perform Action:(SEL)action
with Sender:(id)sender
{
return (action == @selector(copy:));
}
#pragma mark - UIResponder Standard Edit Actions
- (void)copy:(id)sender {
[[UIPasteboard general Pasteboard] set String:self.text];
}
@end
And with that out of the way, the view controller that uses it:
override func view Did Load() {
super.view Did Load()
let label: Hipster Label = ...
label.user Interaction Enabled = true
view.add Subview(label)
let gesture Recognizer = UILong Press Gesture Recognizer(target: self, action: "handle Long Press Gesture:")
label.add Gesture Recognizer(gesture Recognizer)
}
// MARK: - UIGesture Recognizer
func handle Long Press Gesture(recognizer: UIGesture Recognizer) {
if let recognizer View = recognizer.view,
recognizer Super View = recognizer View.superview
{
let menu Controller = UIMenu Controller.shared Menu Controller()
menu Controller.set Target Rect(recognizer View.frame, in View: recognizer Super View)
menu Controller.set Menu Visible(true, animated:true)
recognizer View.become First Responder()
}
}
- (void)view Did Load {
Hipster Label *label = ...;
label.user Interaction Enabled = YES;
[self.view add Subview:label];
UIGesture Recognizer *gesture Recognizer = [[UILong Press Gesture Recognizer alloc] init With Target:self action:@selector(handle Long Press Gesture:)];
[label add Gesture Recognizer:gesture Recognizer];
}
#pragma mark - UIGesture Recognizer
- (void)handle Long Press Gesture:(UIGesture Recognizer *)recognizer {
if (recognizer.state == UIGesture Recognizer State Recognized) {
[recognizer.view become First Responder];
UIMenu Controller *menu Controller = [UIMenu Controller shared Menu Controller];
[menu Controller set Target Rect:recognizer.view.frame in View:recognizer.view.superview];
[menu Controller set Menu Visible:YES animated:YES];
}
}
So, to recap, in order to allow a label’s text to be copied, the following must happen:
-
UILabel
must be subclassed to implementcan
&Become First Responder can
Perform Action:with Sender: - Each performable action must implement a corresponding method that interacts with
UIPasteboard
- When instantiated by a controller, the label must have
user
set toInteraction Enabled YES
(it is not recommended that this be hard-coded into the subclass implementation) - A
UIGesture
must be added to the label (else,Recognizer UIResponder
methods liketouches
are implemented manually in the subclass)Began:with Event: - In the method implementation corresponding to the gesture recognizer action,
UIMenu
must be positioned and made visibleController - Finally, the label must become first responder
If you’re wondering why, oh why, this isn’t just built into UILabel
, well… join the club.
UIMenu Controller
UIMenu
is responsible for presenting edit action menu items. Each app has its own singleton instance, shared
. By default, a menu controller will show commands for any methods in the UIResponder
informal protocol that the responder returns YES
for in can
.
UIResponder Standard Edit Actions
Handling Copy, Cut, Delete, and Paste Commands
Each command travels from the first responder up the responder chain until it is handled; it is ignored if no responder handles it. If a responder doesn’t handle the command in the current context, it should pass it to the next responder.
copy:
This method is invoked when the user taps the Copy command of the editing menu. A subclass of UIResponder typically implements this method. Using the methods of the UIPasteboard class, it should convert the selection into an appropriate object (if necessary) and write that object to a pasteboard.
cut:
This method is invoked when the user taps the Cut command of the editing menu. A subclass of UIResponder typically implements this method. Using the methods of the UIPasteboard class, it should convert the selection into an appropriate object (if necessary) and write that object to a pasteboard. It should also remove the selected object from the user interface and, if applicable, from the application’s data model.
delete:
This method is invoked when the user taps the Delete command of the editing menu. A subclass of UIResponder typically implements this method by removing the selected object from the user interface and, if applicable, from the application’s data model. It should not write any data to the pasteboard.
paste:
This method is invoked when the user taps the Paste command of the editing menu. A subclass of UIResponder typically implements this method. Using the methods of the UIPasteboard class, it should read the data in the pasteboard, convert the data into an appropriate internal representation (if necessary), and display it in the user interface.
Handling Selection Commands
select:
This method is invoked when the user taps the Select command of the editing menu. This command is used for targeted selection of items in the receiving view that can be broken up into chunks. This could be, for example, words in a text view. Another example might be a view that puts lists of visible objects in multiple groups; the select: command could be implemented to select all the items in the same group as the currently selected item.
select
This method is invoked when the user taps the Select All command of the editing menu.All:
In addition to these basic editing commands, there are commands that deal with rich text editing (toggle
, toggle
, and toggle
) and writing direction changes (make
& make
). As these are not generally applicable outside of writing an editor, we’ll just mention them in passing.
UIMenu Item
With iOS 3.2, developers could now add their own commands to the menu controller. As yet unmentioned, but familiar commands like “Define” or spell check suggestions take advantage of this.
UIMenu
has a menu
property, which is an NSArray
of UIMenu
objects. Each UIMenu
object has a title
and action
. In order to have a menu item command display in a menu controller, the responder must implement the corresponding selector.
Just as a skilled coder designs software to be flexible and adaptable to unforeseen use cases, any app developer worth their salt understands the need to accommodate users with different needs from themselves.
As you develop your app, take to heart the following guidelines:
- For every control, think about what you would expect a right-click (control-click) to do if used from the desktop.
- Any time information is shown to the user, consider whether it should be copyable.
- With formatted or multi-faceted information, consider whether multiple kinds of copy commands are appropriate.
- When implementing
copy:
make sure to copy only valuable information to the pasteboard. - For editable controls, ensure that your implementation
paste:
can handle a wide range of valid and invalid input.
If mobile is to become most things to most people, the least we can do is make our best effort to allow users to be more productive. Your thoughtful use of UIMenu
will not go unnoticed.