Back to Blog

Get in Touch

Developer's Notes for UIMenuController

By Dingding Ye December 22, 2010 in iphone, UIKit

Missing

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 "<<", when clicked, allow us to just iterate to the next N items and back. The menu is always visible. In this way, we can provide as many items in the menu as we need to. (Honestly I don't think we should include too many items but sometimes we have to; just remember to arrange the most important menu items into the list first so users don't have to click through to find the most relevant menu items.

Before I arrived at the above implementation using ">>" and "<<", I struggled through several different attempts. This is the initial screenshot showing how the menu looks when populated with many items:



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 < 4; i++)
            [menuItems addObject:[menuActionItems objectAtIndex:i]];
        [menuItems addObject:expandItem];
        [menuController setMenuItems:menuItems];
        [menuController setTargetRect:[[delegate tableView] rectForRowAtIndexPath:pressedIndexPath] inView:[delegate tableView]];
        [menuController setMenuVisible:YES animated:YES];
    }

    - (void)expandMenu {
        UIMenuItem *collapseMenuItem = [[UIMenuItem alloc] initWithTitle:@"<<" action:@selector(collapseMenu)];
        NSMutableArray *expandActionItems = [[NSMutableArray alloc] initWithObjects:collapseMenuItem, nil];
        for (NSInteger i = 4; i < [self.menuActionItems count]; i++)
            [expandActionItems addObject:[menuActionItems objectAtIndex:i]];
        [[UIMenuController sharedMenuController] setMenuItems:expandActionItems];
        [collapseMenuItem release];
        [expandActionItems release];
        [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
    }

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:@"<<" action:@selector(collapseMenu)];
            NSMutableArray *expandActionItems = [[NSMutableArray alloc] initWithObjects:collapseMenuItem, nil];
            for (NSInteger i = 4; i < [self.menuActionItems count]; i++)
                [expandActionItems addObject:[menuActionItems objectAtIndex:i]];
            [[UIMenuController sharedMenuController] setMenuItems:expandActionItems];
            [collapseMenuItem release];
            [expandActionItems release];
            [self performSelector:@selector(doShowMenu) withObject:nil afterDelay:0.5];
        }
    
        - (void)doShowMenu {
            UIMenuController *menuController = [UIMenuController sharedMenuController];
            [menuController setMenuVisible:YES animated:YES];	
        }
    

    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!

Tags:

iphone UIKit
Medium

Dingding Ye

Since graduating form Zhejiang University with a degree in Computer Science in 2006, Dingding has focused on developing high quality software and contributing to open source. He has extensive experience in cloud computing, data mining, and takes an Agile approach to the development process. He lives in Hangzhou, Zhejiang, China with his wife and enjoys playing soccer and working out at the gym during his free time.

More posts by Dingding Ye

Dingding Ye

When Apple introduced the highly anticipated "Copy and Paste" feature for ...

Dingding Ye