71

I'm trying to create a custom UIMenuController and display it in my view. Here's my code:

UIMenuController *menuController = [UIMenuController sharedMenuController];
    UIMenuItem *listMenuItem = [[UIMenuItem alloc] initWithTitle:@"List" action:@selector(addList:)];

    [menuController setMenuItems:[NSArray arrayWithObject:listMenuItem]];
    [menuController setTargetRect:CGRectMake(50.0, 50.0, 0, 0) inView:self.view];
    [menuController setMenuVisible:YES animated:YES];

    [listMenuItem release];

There are no errors or exceptions, but the menu controller just doesn't show up.

indragie
  • 18,002
  • 16
  • 95
  • 164

7 Answers7

182

You need to do three things:

  1. You need to call -becomeFirstResponder on the view or view controller.
  2. Your view or view controller needs to implement -canBecomeFirstResponder (returning YES).
  3. Optionally, your view or view controller can implement -canPerformAction:action withSender:sender to show/hide menu items on an individual basis.
Pang
  • 9,564
  • 146
  • 81
  • 122
OZ Apps
  • 1,836
  • 1
  • 12
  • 4
  • This works really well in MonoTouch too--only things to be aware of is that the `CanBecomeFirstResponder` is a property override, and that you must assign a `UIMenuItem[]` array to the `UIMenuController.MenuItems` property. – PaulJ May 10 '12 at 08:45
  • 6
    Didn't work when I called `becomeFirstResponder` *on the view*. Calling it on the controller worked fine. – Dan Abramov Apr 15 '13 at 17:02
  • 3
    To make sure the call to becomeFirstResponder succeeds, you may check the BOOL it returns is YES. Otherwise you can expect the presentation of UIMenuController to fail. – Dalzhim Dec 12 '13 at 19:44
  • 4
    I got this working on the _view_, as long as I implemented step 3 (not optional). – William Denniss Mar 21 '14 at 08:52
  • Implementing ` -canPerformAction` isn't optional. – Nestor Jul 25 '15 at 19:23
  • I got it to work without step 3. The trick is that if you implement the selector (in the question: "addList"), then the ViewController will automatically return true for canPerformAction. – Alex Wally Aug 31 '15 at 06:34
  • Overriding `- canBecomeFirstResponder` fixed my problem, thx. – Yongqiang Zhou Jan 13 '16 at 07:28
  • Nice answer for saving my life. – tounaobun Apr 15 '16 at 03:31
  • "Your view or view controller needs to implement -canBecomeFirstResponder (returning YES).." That was a key to show it from UITableView header! Thanks! (I already called becomeFirstResponder() on that view) – bojan Jun 02 '20 at 00:06
25

The answer mentions three things, but to be picky, there are six:

  1. The menu handler must be a UIView. If it isn't, -becomeFirstResponder fails.
  2. The menu handler must have userInteractionEnabled = YES
  3. The menu handler must be in the view hierarchy and its -window property must be the same as the window for the view in the inView: argument.
  4. You need to implement -canBecomeFirstResponder and return YES.
  5. You need to call [handler becomeFirstResponder], before [menu setTargetRect:inView:] is called, or the latter will fail.
  6. You need to call [menu setTargetRect:inView] (at least once) and [menu setMenuVisible:animated:].

In particular points 1-3 above got me. I wanted a custom menu handler class that was a UIResponder at first, which caused -becomeFirstResponder to return NO; then it was a UIView, which failed, then I tried making it a UIButton which worked, but only because userInteractionEnabled defaults to YES for buttons and NO for UIViews.

Kalle
  • 13,186
  • 7
  • 61
  • 76
15

UIMenuController is visible on any view only if the view is first responder and

- (BOOL)canPerformAction method returns YES

Hence if your menu controller is to be shown on button click, the first line in the button action should be [self becomeFirstResponder]. NOTE: here self is the view which will present the menus.

If your menus are to be shown on long press gesture, then add longPressGesture to the UIView and in the longpress event before writing

[menuController setTargetRect:CGRectMake(50.0, 50.0, 0, 0) inView:self.view];
[menuController setMenuVisible:YES animated:YES];

write [self becomeFirstResponder];

Then follow the steps mentioned by OZ.

Jake
  • 3,973
  • 24
  • 36
Snehal
  • 597
  • 3
  • 10
  • I tried this but its not working for me. As comment have limit I have created another question http://stackoverflow.com/questions/16054050/uimenucontroller-not-getting-displayed – JiteshW Apr 17 '13 at 07:33
  • Thanks, the targetRect did the trick for me. Weird because I have another almost identical class that works fine without it. – Kalle Aug 09 '13 at 12:05
8

The below is a full commented working example ...

View subclass header file

#import <Foundation/Foundation.h>

@interface MenuControllerSupportingView : UIView
{

}
@end

View subclass source file

#import "MenuControllerSupportingView.h"

@implementation MenuControllerSupportingView

//It's mandatory and it has to return YES then only u can show menu items..
-(BOOL)canBecomeFirstResponder
{
  return YES;
}

-(void)MenuItemAClicked
{
  NSLog(@"Menu item A clicked");
}

-(void)MenuItemBClicked
{
 NSLog(@"Menu item B clicked");
}

-(void)MenuItemCClicked
{
  NSLog(@"Menu item C clicked");
}

//It's not mandatory for custom menu items

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{  
  if(action == @selector(MenuItemAClicked))
     return YES;
  else if(action == @selector(MenuItemBClicked))
    return YES;
  else if(action == @selector(MenuItemCClicked))
    return YES;
  else
    return NO;
}

view Controller header file

#import <UIKit/UIKit.h>

@interface ViewController1 : UIViewController

@end

view Controller source file

 #import "ViewController1.h"
 #import "MenuControllerSupportingView.h"

@interface ViewController1 ()
{
 MenuControllerSupportingView *vu;
}
@end

@implementation ViewController1

 - (void)viewDidLoad
{
  [super viewDidLoad];

  vu=[[SGGI_MenuControllerSupportingView alloc]initWithFrame:CGRectMake(0,0,768,1024)];

[self.view addSubview:vu];

 UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];

 [btn setFrame:CGRectMake(200,200,200,30)];

 [btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];

 [btn setTitle:@"Show" forState:UIControlStateNormal];

 [btn addTarget:self action:@selector(SHowMenu) forControlEvents:UIControlEventTouchUpInside];

 [vu addSubview:btn];

}

-(void)SHowMenu
{
 UIMenuController *menucontroller=[UIMenuController sharedMenuController];

UIMenuItem *MenuitemA=[[UIMenuItem alloc] initWithTitle:@"A" action:@selector(MenuItemAClicked)];

UIMenuItem *MenuitemB=[[UIMenuItem alloc] initWithTitle:@"B" action:@selector(MenuItemBClicked)];

UIMenuItem *MenuitemC=[[UIMenuItem alloc] initWithTitle:@"C" action:@selector(MenuItemCClicked)];

[menucontroller setMenuItems:[NSArray arrayWithObjects:MenuitemA,MenuitemB,MenuitemC,nil]];

    //It's mandatory
[vu becomeFirstResponder];

    //It's also mandatory ...remeber we've added a mehod on view class
if([vu canBecomeFirstResponder])
{

    [menucontroller setTargetRect:CGRectMake(10,10, 0, 200) inView:vu];

    [menucontroller setMenuVisible:YES animated:YES];
}

}




-(void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];

}

@end

In View class if u write return YES alone in canPerformAction you will see all the default menuitems like camera symbol,cut,copy etc..

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
 return YES;
}

if u want to show something like camera alone then

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if(action==@selector(_insertImage:))
     return YES;
else
     return NO;

}

if u want to know about all the actions then

visit the link

Durai Amuthan.H
  • 31,670
  • 10
  • 160
  • 241
  • 1
    very helpful, I added similar code to subclass a UIButton in swift, and works like magic. Thanks. – Ning Jun 27 '20 at 03:00
2

Just in case anyone is having this issue specifically (and randomly) with iOS6: you might want to look at this SO related to having Speak Selection enabled on the device (Settings -> General -> Accessibility -> Speak Selection: On). A small number of my users were not able to see the custom UIMenuItems and this was the cause.

Community
  • 1
  • 1
Matthew Leffler
  • 1,386
  • 1
  • 19
  • 36
1

In Swift 3.0 -

In my case I wanted to have the VC pre-select the text in a TextView and display a custom menu for the user to take action on that selection. As mentioned by Kalle, order is very important, especially making setMenuVisible last.

In VC, viewDidLoad:

menuCont = UIMenuController.shared
let menuItem1: UIMenuItem = UIMenuItem(title: "Text", action: #selector(rtfView.textItem(_:)))
let menuItems: NSArray = [menuItem1]
menuCont.menuItems = menuItems as? [UIMenuItem]

In VC, when the user hits a button:

@IBAction func pressed(_ sender: Any) {
    self.textView.selectedRange = NSMakeRange(rangeStart, rangeLength)
    self.textView.becomeFirstResponder()
    menuCont.setTargetRect(CGRect.zero, in: self.textView)
    menuCont.setMenuVisible(true, animated: true)
}

Finally, in the sub-class of the TextView:

class rtfView: UITextView {

override var canBecomeFirstResponder: Bool {
    return true
}

override func canPerformAction(_ action: Selector, withSender sender: Any!) -> Bool {
    if (action == #selector(textItem(_:))) {
        return true
    } else {
        return false
    }
  }
}
profRic
  • 91
  • 6
-1

maybe because CGRectMake(50.0, 50.0, 0, 0) creates a CGRect with width = 0 and height = 0?

cheers, anka

anka
  • 3,817
  • 1
  • 30
  • 36