I am using UITableViewController. If I long press on a tableview cell I am creating custom menus in the UIMenuController and which is working fine (Forward, Reply). In the same view I have the textview in the bottom. If I tap on it should display the normal actions but it doesn't. It comes with default items as well as what are the items I have added for the tableview cell (Forward, Reply). How to remove the custom items from the UIMenuController or how to perform the action for a particular cell.
Inside the cell I have an UIImageView. I have added gestures to perform the action.
For this, first you create a menu with custom items and then listen to UIMenuControllerWillHideMenuNotification notification. In side this notification when menu is going to hide you can remove the items what you added. Here is the sample code.
-(void) showMenu{
UIMenuController * menuController =[UIMenuController sharedMenuController];
UIMenuItem * item1 = [[UIMenuItem alloc] initWithTitle:#"Goto" action:#selector(menuItem1Clicked:)];
UIMenuItem * item2 = [[UIMenuItem alloc] initWithTitle:#"Edit" action:#selector(menuItem2Clicked:)];
[menuController setMenuItems:#[item, item1]];
[menuController setTargetRect:rect inView:self.view];
[menuController setMenuVisible:YES animated:YES];
}
And when menu is going to hide remove the items you added
-(void) menuControllerWillHide:(NSNotification*)notification
{
UIMenuController * controller = [UIMenuController sharedMenuController];
NSArray * items = [controller menuItems]; // These are all custom items you added
NSMutableArray * finalItemsYouWant = [NSMutableArray array];
// Here you can check what items you dont want and then remove it
[controller setMenuItems:finalItemsYouWant];
}
You should have implemented canPerformAction:withSender: to get your custom items to work. In that method you can verify the sender to check what class / instance it is and decide what to do.
Alternatively, check which instance is the first responder.
Swift3:
in viewDidLoad of ViewController:
NotificationCenter.default.addObserver(self, selector: #selector(menuControllerWillHide), name: NSNotification.Name.UIMenuControllerWillHideMenu, object: nil)
func menuControllerWillHide(notification:NSNotification){
UIMenuController.shared.menuItems = []
}
-(void)showMenu:(UILongPressGestureRecognizer *)longPressRecognizer
{
longPressRowNum = longPressRecognizer.view.tag;
NSIndexPath *indPath = [NSIndexPath indexPathForRow:longPressRecognizer.view.tag inSection:0];
//Type cast it to CustomCell
UITableViewCell *cell = (UITableViewCell*)[projectTable cellForRowAtIndexPath:indPath];
ProjectDashBoard *projectDashBoard = [listRecord objectAtIndex:longPressRecognizer.view.tag];
NSLog(#"long press project status---%#",projectDashBoard.projectStatus);
if (longPressRecognizer.state == UIGestureRecognizerStateBegan) {
UITableViewCell *selectedCell = (UITableViewCell *)longPressRecognizer.view;
[selectedCell setSelected:YES];
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"deleteproject", nil) action:#selector(deleteClicked:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
if([projectDashBoard.projectStatus isEqualToString:statusActive]){
UIMenuItem *archive = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"archiveproject", nil) action:#selector(archiveClicked:)];
[menu setMenuItems:[NSArray arrayWithObjects:delete, archive, nil]];
}else{
UIMenuItem *archive = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"unarchiveproject", nil) action:#selector(archiveClicked:)];
[menu setMenuItems:[NSArray arrayWithObjects:delete, archive, nil]];
}
[[UIMenuController sharedMenuController] update];
[menu setTargetRect:cell.frame inView:cell.superview];
[menu setMenuVisible:YES animated:YES];
}
}
Related
I have a tableview cells with 4 different type of cells. I need to edit only two types of cells. So i have added a long press gesture recognizer to the two cells. Now i need to delete these two kinds of cells. So in the long press gesture recogniser function, i have added a UIMenuController with a delete button as UIMenuItem. Now in the Delete function I am setting the tableview in editable mode.
- (void)Delete:(id)sender {
NSLog(#"\n Delete Selected \n");
[mTableView setEditing:YES animated:YES];
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
-(void)addLongPressGestureRecognizerForCell:(UITableViewCell *)tableViewCell{
UILongPressGestureRecognizer *lLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressGestureFunction:)];
lLongPressGesture.minimumPressDuration = 0.3;
lLongPressGesture.delegate = self;
[tableViewCell addGestureRecognizer:lLongPressGesture];
// [lLongPressGesture setCancelsTouchesInView:YES];
}
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath{
mTableView.allowsSelectionDuringEditing = YES;
UILongPressGestureRecognizer *lLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressGestureFunction:)];
UITableViewCell *lTableViewCell = [tableView cellForRowAtIndexPath:indexPath];
[lTableViewCell removeGestureRecognizer:lLongPressGesture];
}
Now when the table is in editable mode, I am not able to select the two type of tableview cells. Also the delegate method willBeginEditingRowAtIndexPath is not called.
The buttons on the left of the tableView in editable mode are not getting tapped. I only need the buttons on the left to be tapped in edit mode and not the entire cells.
Should I remove the gesture recogniser for the cells in the editable mode?
Should I enable the selection of the cells?
I have tried all the options but no Luck. I am running out of options for this implementation but Alas!!!. Can someone please explain about any change that has to be implemented for the cells to be selectable in edit mode of a tableView?
Below is the code for Long Press Gesture Functionality
- (void)longPressGestureFunction:(UILongPressGestureRecognizer *)recognizer
{
UITableViewCell *lTableViewCell = (UITableViewCell *)recognizer.view;
[lTableViewCell becomeFirstResponder];
UITextView *lMessageTextView = [lTableViewCell viewWithTag:103];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
UIMenuItem *MenuDelete = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(Delete:)];
UIMenuItem *MenuForward = [[UIMenuItem alloc] initWithTitle:#"Forward" action:#selector(Forward:)];
UIMenuItem *MenuAddToContacts = [[UIMenuItem alloc] initWithTitle:#"Add To Contacts" action:#selector(addToContacts:)];
mSharedMenu = [UIMenuController sharedMenuController];
[mSharedMenu setMenuItems:[NSArray arrayWithObjects:MenuDelete, MenuForward, MenuAddToContacts, nil]];
/*Change the position of the target rect based on Sending messages or Receiving messages*/
if ([lTableViewCell.reuseIdentifier isEqualToString:#"SendingChatCellIdentifier"]) {
[mSharedMenu setTargetRect:lMessageTextView.frame inView:lMessageTextView.superview];
}else if ([lTableViewCell.reuseIdentifier isEqualToString:#"ReceivingChatCellIdentifier"]){
UILabel *senderNameLabel = [lTableViewCell viewWithTag:100];
[mSharedMenu setTargetRect:senderNameLabel.frame inView:senderNameLabel.superview];
}
[mSharedMenu setMenuVisible:YES animated:YES];
}
}
Adding long press gesture is requirement or your need? Cause you can detect the tap in didSelectRowAtIndexPath and check for your cell types otherwise add uiview in Uitableview cell and add long press gesture in that uiview .
i am trying to use UIMenuController to perform a custom action on the table cell, from which the UIMenuController triggered by long time press.
I registered UILongPressGestureRecognizer in the viewDidLoad method in my subclass of UITableViewController and added custom item with #selector(handleMyAction).
- (void)viewDidLoad
{
[super viewDidLoad];
[self.refreshControl addTarget:self action:#selector(refreshView:) forControlEvents:UIControlEventValueChanged];
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPressGesture.minimumPressDuration = .5;
longPressGesture.delegate = self;
[self.tableView addGestureRecognizer:longPressGesture];
}
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
if(gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint point = [gestureRecognizer locationInView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:point];
if(indexPath == nil) return ;
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIMenuItem *it = [[UIMenuItem alloc] initWithTitle:#"My Action on this cell" action:#selector(handleMyAction:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:it, nil]];
[menu setTargetRect:cell.frame inView:cell.superview];
[menu setMenuVisible:YES animated:YES];
[self becomeFirstResponder];
}
}
I also override the
- (BOOL)canBecomeFirstResponder{
return YES;
}
When I press on one cell the context menu with the custom entry displays properly. BUT, the problem is how can I implement the method to handle the custom action, which should be performed on the tapped cell.
- (void)handleMyAction:(id)sender
{
NSLog(#"Action triggered, however need some way to refer the tapped cell");
}
Because the only information i can get in this method is the sender, which is the UIMenuController self, but i have no idea how can get the cell, on which the Menu triggered, so i can do further action regarding the cell itself.
Could some one help me on that?
Thanks.
Hai
Well you're currently adding the UIGestureRecognizer to the tableview itself. Why not add it to each cell instead (in cellForRowAtIndexPath when they are setup) ?
Thanks valheru. I find a "nice" approach to achieve that:)
Step one: In MyTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPressGesture.minimumPressDuration = .5;
longPressGesture.delegate = self;
[self.view addGestureRecognizer:longPressGesture];
}
which register the gesture recognizer of the long press on the table view controller.
- (BOOL)canBecomeFirstResponder
{
return YES;
}
which allows MyTableViewController response the long press and popup the context menu.
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
if(gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint point = [gestureRecognizer locationInView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:point];
if(indexPath == nil) return ;
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:indexPath];
UIMenuItem *determine = [[UIMenuItem alloc] initWithTitle:#"My Action on this cell" action:#selector(handleMyAction:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:determine, nil]];
[menu setTargetRect:cell.frame inView:cell.superview];
[menu setMenuVisible:YES animated:YES];
[cell becomeFirstResponder]; //here set the cell as the responder of the menu action
cell.delegate = self;// this is optional, if you don't want to implement logic in cell class
}
}
create the UIMenuController and popup when i long press the cell.
-(void)handleMyAction: (UITableViewCell *)cell
{
NSLog(#"%#", cell);
}
this function will be called later from the cell which is pressed.
Step two: Create a subclass of UITableViewCell named MyCell
In MyCell.h defines the table view controller the cell belongs as the delegate of the cell. And the callback function when the menu entry clicked
#property (nonatomic, strong) MyTableViewController *delegate;
-(void)handleMyAction:(id)sender;
in MyCell.m
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if(action == #selector(handleMyAction:))
{
return YES;
}
return NO;
}
allows the MyCell to be the first responder and response the handleMyAction action by clicking on menu entry.
-(void)handleMyAction:(id)sender
{
[self.delegate handleMyAction:self]; //it's a coincidence both functions have the same name:)
}
this is the definition of the callback function, which will be called when click on the menu entry, and it in turn call the handMyAction function in the delegate of the cell (MyTableViewController, where the logic regarding the cell could be implemented.)
First declare a NSIndexPath type as a class variable.
#property (nonatomic, strong) NSIndexPath *savedIndexPathForThePressedCell;
Now in the Long press gesture recognizer function, get the TableViewCell using the recognizer view. Now save the IndexPath for the TableViewCell
- (void)longPressGestureFunction:(UILongPressGestureRecognizer *)recognizer
{
UITableViewCell *lTableViewCell = (UITableViewCell *)recognizer.view;
[lTableViewCell becomeFirstResponder];
/*Save the Indexpath of the cell pressed*/
self.savedIndexPathForThePressedCell = [mTableView indexPathForCell:lTableViewCell];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
UIMenuItem *MenuDelete = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(Delete:)];
UIMenuItem *MenuForward = [[UIMenuItem alloc] initWithTitle:#"Forward" action:#selector(Forward:)];
UIMenuItem *MenuAddToContacts = [[UIMenuItem alloc] initWithTitle:#"Add To Contacts" action:#selector(addToContacts:)];
mSharedMenu = [UIMenuController sharedMenuController];
[mSharedMenu setMenuItems:[NSArray arrayWithObjects: MenuDelete, MenuForward, nil]];
[mSharedMenu setMenuVisible:YES animated:YES];
}
Now in the Menu selector method, select the row based on the saved indexPath.
- (void)Delete:(id)sender {
// NSLog(#"\n Delete Selected \n");
[mTableView setEditing:YES animated:YES];
[mTableView selectRowAtIndexPath:self.savedIndexPathForThePressedCell animated:YES scrollPosition:UITableViewScrollPositionNone];
}
I hope this helps!!!
I have a UITableView that displays the data from my plist. Is there any way I could edit/delete the row's data using UIMenuController?
It would be fine to use the traditional swipe-the-row in order to edit/delete the row, but I am using UISwipeGestureRecognizer to display my secondViewController - this view controller is where I could add a data to my plist.
The UIMenuItem shows up, but I don't know how to proceed from here.
It would be greatly appreciated if someone could walk me through.
This is my longPressHandler which triggers my UIMenuController.
- (void)longPressHandler:(UILongPressGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan) {
[self becomeFirstResponder];
CGPoint point = [sender locationInView:[self myTableView]];
NSIndexPath *indexPath =[[self myTableView] indexPathForRowAtPoint:point];
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *editFruit = [[UIMenuItem alloc] initWithTitle:#"Edit" action:#selector(editFruit)];
UIMenuItem *deleteFruit = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(deleteFruit)];
[menuController setTargetRect:[[self myTableView] rectForRowAtIndexPath:indexPath]
inView:[self myTableView]];
[menuController setMenuItems:[NSArray arrayWithObjects:editFruit, deleteFruit, nil]];
[menuController setMenuVisible:YES
animated:YES];
}
}
This is where it ends.
- (void)deleteFruit
{
NSLog(#"Delete.");
}
- (void)editFruit
{
NSLog(#"Edit.");
}
It looks like you have all you need there.
So in your deleteFruit method, simply delete the item at that indexPath you've found, and call -deleteRowsAtIndexPaths:withRowAnimation: on the tableview.
In my application, i've to show a menu controller on tapping a table view cell. Its showing a menu. As well as all the actions are being executed successfully. Good till now.
One minor problem I'm facing is that, I'm not able to hide the menu controller if the cell (or some other cell) is tapped again. Here's the code that I'm using:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UIMenuController* menuController = [UIMenuController sharedMenuController];
if ([menuController isMenuVisible])
{
[menuController setMenuVisible:NO animated:YES];
}
else
{
[self becomeFirstResponder];
self.selectedIndex = indexPath.row;
[menuController setTargetRect:[tableView rectForRowAtIndexPath:indexPath] inView:tableView];
[menuController setMenuItems:#[
[[UIMenuItem alloc] initWithTitle:#"Play" action:#selector(playVideo:)],
[[UIMenuItem alloc] initWithTitle:#"Edit" action:#selector(editVideo:)],
[[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(deleteVideo:)],
[[UIMenuItem alloc] initWithTitle:#"Share" action:#selector(shareVideo:)],
[[UIMenuItem alloc] initWithTitle:#"Cancel" action:#selector(cancelMenu:)]
]];
menuController.arrowDirection = UIMenuControllerArrowUp;
[menuController setMenuVisible:YES animated:YES];
}
}
I don't know why is it not hiding on tapping the table view cell again. Can some one guide me on what's the mistake that I'm doing?
In my experience, dismissing the menu controller with [menuController setMenuVisible:NO animated:NO]; has helped. I suppose if you try to animate a menu out and in in the same block of code you might run into problems.
I have a ballon view and when I tap on cell UIMenuController doesn't appear. I tried to implement in
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath,
then I added a long press gesture
(void)longPress:(UILongPressGestureRecognizer *)recognizer,
in the end I added an implementation in extended
UITableViewCell
class, but it still doesn't work.
Here is an example
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender
{
return YES;
}
-(void)copyAction:(id)sender{
NSLog(#"copy");
}
-(void)deleteAction:(id)sender{
NSLog(#"delete");
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
BalloonCell *cell = (BalloonCell*)[tableView cellForRowAtIndexPath:indexPath];
[self becomeFirstResponder];
BOOL isCanBecomde = [self canBecomeFirstResponder];
BOOL isFirst = [self isFirstResponder];
UIMenuItem *copy = [[UIMenuItem alloc] initWithTitle:#"Копировать" action:#selector(copyAction:)];
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:#"Удалить" action:#selector(deleteAction:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
CGRect drawRect = [cell convertRect:cell.bounds toView:self.view];
[menu setTargetRect:drawRect inView:window];
[menu setMenuItems:[NSArray arrayWithObjects:copy, delete, nil]];
[menu setMenuVisible:YES animated:YES];
}
Do you have any guess why UIMenuController doesn't appear?