Displaying and Managing the Edit Menu

The edit menu is a contextual menu that is displayed to offer commands that can be performed on a selection such as a word in a text view or an image. The edit menu is an integral part of copy, cut, and paste operations, for which it displays (potentially) the commands Copy, Cut, Paste, Select, and Select All. However, you can add custom menu items to the edit menu to perform other kinds of actions on selections.

Managing the Selection and the Edit Menu

To copy or cut something in a view, or to do anything else with it, that “something” must be selected. It can be a range of text, an image, a URL, a color, or any other representation of data, including custom objects. You must manage the selection of objects in that view yourself. If the user selects an object in the view by making a certain touch gesture (for example, a double-tap) you must handle that event, internally record the selection (and deselect any previous selection), and perhaps visually indicate the new selection in the view. If it is possible for users to select multiple objects in your view for copy-cut-paste operations, you must implement that multiple-selection behavior.

When your app determines that the user has requested the edit menu—which could be the action of making a selection—you should complete the following steps to display the menu:

  1. Call the sharedMenuController class method of UIMenuController to get the global menu-controller instance.

  2. Compute the boundaries of the selection and with the resulting rectangle call the setTargetRect:inView: method. The edit menu is displayed above or below this rectangle, depending how close the selection is to the top or bottom of the screen.

  3. Call the setMenuVisible:animated: method (with YES for both arguments) to animate the display of the edit menu above or below the selection.

Listing 8-1 illustrates how you might display the edit menu in an implementation of the touchesEnded:withEvent: method for handling copy, cut, and paste operations. (Note that the example omits the section of code that handles the selection.) This code snippet also shows the custom view sending itself a becomeFirstResponder message to ensure that it is the first responder for the subsequent copy, cut, and paste operations.

Listing 8-1  Displaying the edit menu

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
 
    if ([theTouch tapCount] == 2  && [self becomeFirstResponder]) {
 
        // selection management code goes here...
 
        // bring up edit menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];
 
    }
}

The menu initially includes all commands for which the first responder has corresponding UIResponderStandardEditActions method implementations (copy:, paste:, and so on). Before the menu is displayed, however, the system sends a canPerformAction:withSender: message to the first responder, which in many cases is the custom view itself. In its implementation of this method, the responder evaluates whether the command (indicated by the selector in the first argument) is applicable in the current context. For example, if the selector is paste: and there is no data in the pasteboard of a type the view can handle, the responder should return NO to suppress the Paste command. If the first responder does not implement the canPerformAction:withSender: method, or does not handle the given command, the message travels up the responder chain.

Listing 8-2 shows an implementation of the canPerformAction:withSender: method that looks for message matching the cut:, copy:, and paste: selectors; it enables or disables the Copy, Cut, and Paste menu commands based on the current selection context and, for paste, the contents of the pasteboard.

Listing 8-2  Conditionally enabling menu commands

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    BOOL retValue = NO;
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
 
    if (action == @selector(paste:) )
        retValue = (theTile == nil) &&
             [[UIPasteboard generalPasteboard] containsPasteboardTypes:
             [NSArray arrayWithObject:ColorTileUTI]];
    else if ( action == @selector(cut:) || action == @selector(copy:) )
        retValue = (theTile != nil);
    else
        retValue = [super canPerformAction:action withSender:sender];
    return retValue;
}

Note that the final else clause in this method calls the superclass implementation to give any superclass a chance to handle commands that the subclass chooses to ignore.

Note that a menu command, when acted upon, can change the context for other menu commands. For example, if the user selects all objects in the view, the Copy and Cut commands should be included in the menu. In this case the responder can, while the menu is still visible, call update on the menu controller; this results in the reinvocation of canPerformAction:withSender: on the first responder.

Adding Custom Items to the Edit Menu

You can add a custom item to the edit menu. When users tap this item, a command is issued that affects the current target in an app-specific way. The UIKit framework accomplishes this through the target-action mechanism. The tap of an item results in an action message being sent to the first object in the responder chain that can handle the message. Figure 8-1 shows an example of a custom menu item (“Change Color”).

Figure 8-1  An edit menu with a custom menu item

An instance of the UIMenuItem class represents a custom menu item. UIMenuItem objects have two properties, a title and an action selector, which you can change at any time. To implement a custom menu item, you must initialize a UIMenuItem instance with these properties, add the instance to the menu controller’s array of custom menu items, and then implement the action method for handling the command in the appropriate responder subclass.

Other aspects of implementing a custom menu item are common to all code that uses the singleton UIMenuController object. In a custom or overridden view, you set the view to be the first responder, get the shared menu controller, set a target rectangle, and then display the edit menu with a call to setMenuVisible:animated:. The simple example in Listing 8-3 adds a custom menu item for changing a custom view’s color between red and black.

Listing 8-3  Implementing a Change Color menu item

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
    if ([theTouch tapCount] == 2) {
        [self becomeFirstResponder];
        UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)];
        UIMenuController *menuCont = [UIMenuController sharedMenuController];
        [menuCont setTargetRect:self.frame inView:self.superview];
        menuCont.arrowDirection = UIMenuControllerArrowLeft;
        menuCont.menuItems = [NSArray arrayWithObject:menuItem];
        [menuCont setMenuVisible:YES animated:YES];
    }
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
 
- (BOOL)canBecomeFirstResponder { return YES; }
 
- (void)changeColor:(id)sender {
    if ([self.viewColor isEqual:[UIColor blackColor]]) {
        self.viewColor = [UIColor redColor];
    } else {
        self.viewColor = [UIColor blackColor];
    }
    [self setNeedsDisplay];
}

Dismissing the Edit Menu

When your implementation of a system or custom command returns, the edit menu is automatically hidden. You can keep the menu visible with the following line of code:

[UIMenuController sharedMenuController].menuVisible = YES;

The system may hide the edit menu at any time. For example, it hides the menu when an alert is displayed or the user taps in another area of the screen. If you have state or a display that depends on whether the edit menu is visible, you should listen for the notification named UIMenuControllerWillHideMenuNotification and take an appropriate action.