20

I'm trying to get the following code work:

UIMenuController * menu = [UIMenuController sharedMenuController];
[menu setTargetRect: CGRectMake(100, 100, 100, 100) inView: self.view];
[menu setMenuVisible: YES animated: YES];

The menu instance is ready but it doesn't show - the width is always zero.

Or is there some sample code on this UIPasteboard/UIMenuController topic?

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
al_lea
  • 596
  • 1
  • 9
  • 17

9 Answers9

52

I was not able to get it working even when I read all of your answers. I'm presenting ready code that will work for everyone.

Let's say we have a controller class named Controller. You can simply paste the following code to this controller to have the menu working on its view:


- (void)loadView {
    [super loadView];

    UILongPressGestureRecognizer *gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    [self.view addGestureRecognizer:gr];    
}

- (void) longPress:(UILongPressGestureRecognizer *) gestureRecognizer {
    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
        CGPoint location = [gestureRecognizer locationInView:[gestureRecognizer view]];
        UIMenuController *menuController = [UIMenuController sharedMenuController];
        UIMenuItem *resetMenuItem = [[UIMenuItem alloc] initWithTitle:@"Item" action:@selector(menuItemClicked:)];

        NSAssert([self becomeFirstResponder], @"Sorry, UIMenuController will not work with %@ since it cannot become first responder", self);
        [menuController setMenuItems:[NSArray arrayWithObject:resetMenuItem]];
        [menuController setTargetRect:CGRectMake(location.x, location.y, 0.0f, 0.0f) inView:[gestureRecognizer view]];
        [menuController setMenuVisible:YES animated:YES];
    }
}

- (void) copy:(id) sender {
    // called when copy clicked in menu
}

- (void) menuItemClicked:(id) sender {
    // called when Item clicked in menu
}

- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender {
    if (selector == @selector(menuItemClicked:) || selector == @selector(copy:)) {
        return YES;
    }
    return NO;
}

- (BOOL) canBecomeFirstResponder {
    return YES;
}

What has to be done in order for menu to work is that the firstResponder(in our case our controller - see line with [self becomeFirstResponder]) has to be able to become first responder (override method canBecomeFirstResponder cause default implementation returns NO) as well as - (BOOL) canPerformAction:(SEL)selector withSender:(id) sender which should return YES to any action that can be performed by firstResponder

William Jockusch
  • 26,513
  • 49
  • 182
  • 323
krasnyk
  • 3,458
  • 3
  • 24
  • 20
  • 5
    You might want to change that assertion to use canBecomeFirstResponder, and put the becomeFirstResponder outside the assert ;) – antsyawn Jan 07 '11 at 01:04
  • This works perfectly. I was a bit concerned at first that [self becomeFirstResponder] will bring the keyboard up, but it didn't. Thank you! – R.S May 18 '13 at 04:21
  • In the copy method, how can I get the selected text? to copy to the UIPasteboard – Khaled Annajar May 11 '15 at 11:45
7

If you're implementing a custom view and that view is supposed to be the responder (as opposed to some other view, like a UITextField), you need to override the canBecomeFirstResponder function in your view and return YES:

- (BOOL)canBecomeFirstResponder {
    return YES;
}

then, when you're displaying the menu, you should do something like the following:

- (void)myMenuFunc {
    if (![self becomeFirstResponder]) {
        NSLog(@"couldn't become first responder");
        return;
    }

    UIMenuController *theMenu = [UIMenuController sharedMenuController];
    CGRect selectionRect = CGRectMake(0, 0, 0, 0);
    [theMenu setTargetRect:selectionRect inView:self];
    [theMenu setMenuVisible:YES animated:YES];
}
Cam
  • 791
  • 1
  • 4
  • 4
7

In case somebody still has problems: My menu used to work and some day stopped working miraculously. Everything else in my app still worked. Now I had removed the [window makeKeyAndVisible] method from application:didFinishLaunchingWithOptions: method, and while everything else still worked, this breaks UIMenuController!

Stupid error on my side, difficult to find the culprit...

Pascal
  • 16,846
  • 4
  • 60
  • 69
  • 1
    Thank you for this. Turns out it happend to me, not sure how the makeKeyAndVisible line got removed but without it I almost destroyed my monitor and used it's sharp edges to commit Hari-Kari. – Rob Booth May 24 '12 at 00:36
  • 1
    Thank you very much. I was presenting a loupe in its own window, and needed to make the main window key again for UIMenuController to work. – hatfinch Jun 12 '12 at 18:18
4

to display UIMenuController one has to add following

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    if (action == @selector(cut:))
        return NO;
    else if (action == @selector(copy:))
        return YES;
    else if (action == @selector(paste:))
        return NO;
    else if (action == @selector(select:) || action == @selector(selectAll:))
        return NO;
    else
        return [super canPerformAction:action withSender:sender];
}
markpasc
  • 873
  • 8
  • 14
JOA80
  • 527
  • 3
  • 13
3

I think Cam is right, need override both canPerformAction and canBecomeFirstResponder

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

- (BOOL)canBecomeFirstResponder {
    return YES;
}
Joe Yang
  • 1,615
  • 14
  • 10
1
// MyView.h

@interface MyView : UIView {
    IBOutlet UITextField * textField_;
}

@end

// MyView.m

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"show menu");

    [textField_ becomeFirstResponder];
    // [self.window becomeFirstResponder];

    UIMenuController * menu = [UIMenuController sharedMenuController];
    [menu setTargetRect: CGRectMake(0, 0, 100, 10) inView: self];
    [menu setMenuVisible: YES animated: YES];

    NSLog(@"menu width %f, visible %d", menu.menuFrame.size.width, menu.menuVisible);
}

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

You need to add more code in canPerformAction:withSender: - it should check the pasteboard and your selection state. Apple's iPhone Application Programming Guide provide several code snippets.

markpasc
  • 873
  • 8
  • 14
al_lea
  • 596
  • 1
  • 9
  • 17
0

Don't you have to add the UIMenuController* menu to the main or subview, E.G. self.view? I think it's something like [self.view addSubView:menu.view]; Or am I missing the point of your question. You might also want to set the frame of the menu's view.

dlamblin
  • 43,965
  • 20
  • 101
  • 140
0

UIMenuController doesn't have a view. I just searched some code from apple's iPhone Application Programming Guide: Event Handling:

Listing 3-4 Displaying the editing 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 editing menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake(currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];
    }
}
markpasc
  • 873
  • 8
  • 14
al_lea
  • 596
  • 1
  • 9
  • 17
  • the comment has the characters limit - so i post the code here. – al_lea Jul 18 '09 at 07:19
  • just found a workaround: Put an invisible UITextField in the view, and make it the first responder. Then the copy-paste menu will popup correctly. – al_lea Jul 18 '09 at 08:28
  • I'm having this same problem (my custom view is a subclass of UITableViewCell; but otherwise it's the same situation). An invisible UITextField isn't helping me. The menu is still not showing and the menuFrame is still all zeros. Also, the keyboard pops up whenever I make the UITextField a firstResponder. Can you post some sample code for your work-around? – James Williams Jul 18 '09 at 16:08
0

I did it in the following way below. Just call the method that shows the menu after very short delay in init. I didn't want to call it from View Controller and also didn't find an event that indicates that my custom view appeared and i'm ready to show menu. So this way OK from my perspective. Delay can be less, but its up to you.

@implementation DTSignatureImageView

- (id)initWithImage:(UIImage *)image
{
    self = [super initWithImage:image];
    if(self){
        self.contentMode = UIViewContentModeScaleAspectFit;
        self.frame = CGRectMake(0, 0, image.size.width / 2.5, image.size.height / 2.5);
        self.userInteractionEnabled = YES;

        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(signatureDidPan:)];
        [self addGestureRecognizer:pan];

        [self becomeFirstResponder];

        [self performSelector:@selector(showMenu) withObject:nil afterDelay:0.5];
    }

    return self;
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)showMenu
{
    UIMenuController *menu = [UIMenuController sharedMenuController];
    menu.menuItems = @[
       [[UIMenuItem alloc] initWithTitle:@"Apply" action:@selector(applySignature)],
       [[UIMenuItem alloc] initWithTitle:@"Update" action:@selector(updateSignature)],
       [[UIMenuItem alloc] initWithTitle:@"Clear" action:@selector(delegateSignature)]];
    [menu setTargetRect:self.bounds inView:self];
    [menu setMenuVisible:YES animated:YES];
}

- (NSArray *)menuActions
{
    static NSArray *actions = nil;
    if (actions == nil){
        actions = @[
                    NSStringFromSelector(@selector(applySignature)),
                    NSStringFromSelector(@selector(updateSignature)),
                    NSStringFromSelector(@selector(delegateSignature))];
    }

    return actions;
}

- (void) signatureDidPan: (UIPanGestureRecognizer *)gesture
{
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan: {
            [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
            break;
        }

        case UIGestureRecognizerStateEnded: {
            [self becomeFirstResponder];
            [self showMenu];
        }

        default:
            break;
    }

    CGPoint point = [gesture locationInView:gesture.view.superview];
    gesture.view.center = point;
}
Madman
  • 3,171
  • 2
  • 32
  • 44