tag iphone, UIKit

Developer's Notes for UIMenuController

When Apple introduced the highly anticipated "Copy and Paste" feature for the iPhone, it came with a default menu editing implementation. With the release of iOS 3.2, this implementation has been updated to include configurable menus to ease the iPhone developer's job. It's simple and elegant. In addition to using UIActionSheet to provide a list of commands/actions to choose from, the UIMenuController is worth a try too.

While working with the UIMenuController I struggled for several hours and couldn't find any helpful information even after an exhaustive Google search. So I hope this blog post will help to save you some time and frustration if you are trying to do the same thing. Otherwise, I'm sure this post can help you understand how to use UIMenuController better.

Below is an example of the UIMenuController after I found the best implementation; this is a screenshot from the Present.ly iPhone client which is currently in development.



Notice that the ">>" and "

Before I arrived at the above implementation using ">>" and "



The font size is small and what's worse is that when you add a new item the title will automatically shrink. That's why I need ">>" to expand those less critical but still useful items. So my first version at adding the ">>" is as follows:

    - (void)setMenuItems {
        ...
        [self becomeFirstResponder];
        UIMenuItem *expandItem = [[UIMenuItem alloc] initWithTitle:@">>" action:@selector(expandMenu)];
        NSMutableArray *menuItems = [[NSMutableArray alloc] initWithCapacity:5];
        for (NSInteger i = 0; i 

Unfortunately, this did not work because when I clicked the screen or an item in the menu, the menu would automatically hide. In the last section of "Display and Managing the Edit Menu", it says:

  • 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 setMenuController].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.

  • But either I couldn't find the "setMenuController" method or the [UIMenuController sharedMenuController].menuVisible = YES didn't work. So the next step I tried was with UIMenuControllerWillHideMenuNotification and UIMenuControllerDidHideMenuNotification and I set the menuVisible to YES there.

    - (void)willHideEditMenu:(id)sender {
        LogDebug(@"Will hide edit menu: %d", [UIMenuController sharedMenuController].menuVisible);
        [UIMenuController sharedMenuController].menuVisible = TRUE;
    }

    - (void)didHideEditMenu:(id)sender {
        LogDebug(@"Did hide edit menu: %d", [UIMenuController sharedMenuController].menuVisible);
        [UIMenuController sharedMenuController].menuVisible = TRUE;
    }

    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willHideEditMenu:) name:UIMenuControllerWillHideMenuNotification object:nil];    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didHideEditMenu:) name:UIMenuControllerDidHideMenuNotification object:nil];
    }

As I feared, it still didn't work because the flow is:


The property menuVisible is always TRUE in those methods. So I doubt after UIMenuControllerDidHideMenuNotification, the SDK will do more work such as set the menuVisible to false and then hide the menu. Maybe accessing the private library would help but Apple doesn't allow that.

Look at the flow again. It doesn't work but at least we know that it works when there is no menu in the screen and the menu is hidden after UIMenuControllerDidHideMenuNotification. Keeping these two points in mind I then updated the expandMenu method to let the menu show action delay for a while.

    - (void)expandMenu {
        UIMenuItem *collapseMenuItem = [[UIMenuItem alloc] initWithTitle:@"

Wow, it works as expected! Note that I take 0.5 as the delay period which I think is a reasonable value. You can also execute this method in the didHideEditMenu to be safer.

A few additional notes about the UIMenuController, for your benefit:

  • In order to show menus, you should implement canBecomeFirstResponder in your controller and then invoke [self becomeFirstResponse] before showing the menu.
        - (BOOL)canBecomeFirstResponder {
            return YES;
        }
    
  • You can decide which methods can be executable by implementing canPerformAction:withSender:
        - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
            BOOL retValue = NO;
            
            if (action == @selector(expandMenu:) )
                 [NSArray arrayWithObject:ColorTileUTI]];
                retValue = YES;
            else
                retValue = [super canPerformAction:action withSender:sender];
            return retValue;
        }
    

Hopefully my notes and observations will be helpful to other iPhone developers. Happy coding!