12

I am displaying custom UIMenuController in my tableview like this

enter image description here

but issue is that it is displaying in the center I want to display it on top of label which is orange colored. For displaying on top of label I did this [menu setTargetRect:CGRectMake(10, 10, 0, 0) inView:self.lbl]; below is the entire code.

But if I am displaying the UIMenuController without UITableView setTargetRect works fine.

Why setTargetRect is not working with UITableView?

setTargetRect Doc says:

(a) This target rectangle (targetRect) is usually the bounding rectangle of a selection. UIMenuController positions the editing menu above this rectangle; if there is not enough space for the menu there, it positions it below the rectangle. The menu’s pointer is placed at the center of the top or bottom of the target rectangle as appropriate.

(b) Note that if you make the width or height of the target rectangle zero, UIMenuController treats the target area as a line or point for positioning (for example, an insertion caret or a single point).

(c) Once it is set, the target rectangle does not track the view; if the view moves (such as would happen in a scroll view), you must update the target rectangle accordingly.

What I am missing?

MyViewController

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 5;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TheCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cid" forIndexPath:indexPath];

    cell.lbl.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row];

    return cell;
}

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

-(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return YES;
}

- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    // required
}

MyCustomCell

- (void)awakeFromNib {
    // Initialization code

    UIMenuItem *testMenuItem = [[UIMenuItem alloc] initWithTitle:@"Test" action:@selector(test:)];
    UIMenuController *menu = [UIMenuController sharedMenuController];
    [menu setMenuItems: @[testMenuItem]];

    [menu setTargetRect:CGRectMake(10, 10, 0, 0) inView:self.lbl];

    [menu update];
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

-(BOOL) canPerformAction:(SEL)action withSender:(id)sender {
    return (action == @selector(copy:) || action == @selector(test:));
}

/// this methods will be called for the cell menu items
-(void) test: (id) sender {
    NSLog(@"test");
}

-(void) copy:(id)sender {
    UIMenuController *m = sender;
    NSLog(@"copy");
}
S.J
  • 3,063
  • 3
  • 33
  • 66
  • I'm pretty sure since you are trying to use a custom rect that you should return NO for `shouldShowMenuForRowAtIndexPath`, which will also make the `canPerformAction` and `performAction` methods unnecessary as well. – Andrew Jul 04 '15 at 14:31
  • My only other concern is that the label wouldn't have been inserted into the view hierarchy at the time you config the `UIMenuController`, which might be a problem. You _may_ need to move the `UIMenuController` config code to a different delegate method such as `tableView:willDisplayCell`, or maybe even in the cell's `UIView` method [`didMoveToSuperview`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html#//apple_ref/occ/instm/UIView/didMoveToSuperview) to make sure the cell is actually on screen before you set your custom rect. – Andrew Jul 04 '15 at 14:38
  • @SantaClaus Nope nothing is working, my menu still appearing in center. – S.J Jul 04 '15 at 16:02

1 Answers1

15

1) First, please do this in your view controller's viewDidLoad method.

 UIMenuItem *testMenuItem = [[UIMenuItem alloc] initWithTitle:@"Test"
    action:@selector(test:)];
[[UIMenuController sharedMenuController] setMenuItems: @[testMenuItem]];
 [[UIMenuController sharedMenuController] update];

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuControllerWillShow:) name:UIMenuControllerWillShowMenuNotification object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuControllerWillHide:) name:UIMenuControllerWillHideMenuNotification object:nil];

2) Then, add these two Methods. and set menuFrame inView to your cell

-(void)menuControllerWillShow:(NSNotification *)notification{
 //Remove Will Show Notification to prevent
 // "menuControllerWillShow" function to be called multiple times
 [[NSNotificationCenter defaultCenter]removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil];

//Hide the Original Menu View
UIMenuController* menuController = [UIMenuController sharedMenuController];
CGSize size = menuController.menuFrame.size;
CGRect menuFrame;
menuFrame.origin.x = self.tableView.frame.origin.x;
menuFrame.size = size;
[menuController setMenuVisible:NO animated:NO];

//Modify its target rect and show it again to prevent glitchy appearance
   [menuController setTargetRect:menuFrame inView:cell];
    [menuController setMenuVisible:YES animated:YES];
}

 -(void)menuControllerWillHide:(NSNotification *)notification{
    //re-register menuControllerWillShow
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuControllerWillShow:)
 name:UIMenuControllerWillShowMenuNotification object:nil];
 }

3) Implement tableView Delegates and implement these methods.

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    cell = (TableViewCell*)[tableView cellForRowAtIndexPath:indexPath];

    return YES;
}
-(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return NO;
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    // required
}

4) Setup the cells (subclassing UITableViewCell) with

 -(BOOL) canPerformAction:(SEL)action withSender:(id)sender {
    return (action == @selector(copy:) || action == @selector(test:)); } /// this methods will be called for the cell menu items
 -(void) test: (id) sender {
     NSLog(@"test"); }

 -(void) copy:(id)sender {
   NSLog(@"copy"); }
Moiz Irshad
  • 700
  • 1
  • 6
  • 15
  • 1
    To get `cell` in `menuControllerWillShow`, here is code: `let menuOrigin = self.view.convertPoint(menuController.menuFrame.origin, toView: self.tableView) let indexPath = _chatTableView.indexPathForRowAtPoint(menuOrigin) let cell = _chatTableView.cellForRowAtIndexPath(indexPath!)` - Swift code – D4ttatraya Mar 25 '16 at 16:28
  • This does work, but let say the selected target menu is copy text, then I want to paste in in a chatbox at the uitextview/uitextfield at the bottom of the same screen, the uimenucontroller will still display and float at the previously target rect. This only occur if the textfield to paste is on the same screen, not in the same tableview. Any solution to that? – Dan Jan 05 '19 at 08:24