I have an application with a UITabBar. Every tab is a UINavigationController with several UIViewControllers inside. One of those View Controllers contains a UITableView, and I want to display a floating menu for deleting the long pressed UITableViewCell.
I am using UIMenuController but it's not displayed because the cell refuses to become first responder.
Here's my code:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
lpgr.minimumPressDuration = 1.0; //second
[table addGestureRecognizer:lpgr];
[lpgr release];
}
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gestureRecognizer locationInView: table];
NSIndexPath *indexPath = [table indexPathForRowAtPoint:p];
if (indexPath != nil) {
UITableViewCell* cell = [self tableView:table cellForRowAtIndexPath: indexPath];
[cell becomeFirstResponder];
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(delete:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:delete, nil]];
[menu setTargetRect:cell.frame inView:cell.superview];
[menu setMenuVisible:YES animated:YES];
}
}
}
On the UITableViewCell I have overriden the method:
-(BOOL) canBecomeFirstResponder {
return YES;
}
Any ideas on why the cell is not becoming the first responder?
Thanks!
I think I fixed your code :-)
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gestureRecognizer locationInView: self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:p];
if (indexPath != nil) {
[self becomeFirstResponder];
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(customDelete:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:delete, nil]];
[menu setTargetRect:[self.tableView rectForRowAtIndexPath:indexPath] inView:self.tableView];
[menu setMenuVisible:YES animated:YES];
}
}
}
- (void)customDelete:(id)sender {
//
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == #selector(customDelete:) ){
return YES;
}
return NO;
}
I did the following..
[self becomeFirstResponder];
[menu setTargetRect:[self.tableView rectForRowAtIndexPath:indexPath] inView:self.tableView];
Added true for canPerformAction Not mostly needed if you have implemented the function
Please change the tableview and other things to your local reference variables :-)
Good read - http://www.intridea.com/blog/2010/12/22/developers-notes-for-uimenucontroller
Related
I am attempting the following:
Within each cell I have date-from and date-to UITextField. When the user presses either TextField an InputView of the DatePicker appears with a Done button to complete.
My challenge is I only want the user to be able to select either of the TextFields inside the selected cell and no other. The user must press Done on InputView when they have finished, so the app can validate and reload TableView if necessary.
I have code that manages normal cell selection well when the 'keyboard' is shown, but cannot manage the UITextField behavior as I would like.
I share some code in the hope someone can help me:
- (void)keyboardWillShow:(NSNotification*)aNotification {
self.TableViewDiary.allowsSelection = NO;
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification {
self.TableViewDiary.allowsSelection = YES;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ActivityDiaryCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ActivityDiaryCellId"];
cell.activity = [self.activitycollection objectAtIndex:indexPath.row];
cell.LabelName.text = cell.activity.name;
cell.TextFieldStartDt.text = [self FormatPrettyTime :cell.activity.startdt];
cell.TextFieldEndDt.text = [self FormatPrettyTime :cell.activity.enddt];
cell.datePickerStart.date = cell.activity.startdt;
cell.datePickerEnd.date = cell.activity.enddt;
cell.tag = indexPath.row;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
ActivityDiaryCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
self.datePickerStart = [[UIDatePicker alloc] init];
self.datePickerStart.datePickerMode = UIDatePickerModeDateAndTime;
//self.datePickerStart.date = self.activity.startdt;
[self.datePickerStart addTarget:self action:#selector(datePickerStartValueChanged:) forControlEvents:UIControlEventValueChanged]; // method to respond to changes in the picker value
self.datePickerEnd = [[UIDatePicker alloc] init];
self.datePickerEnd.datePickerMode = UIDatePickerModeDateAndTime;
//self.datePickerEnd.date = self.activity.enddt;
[self.datePickerEnd addTarget:self action:#selector(datePickerEndValueChanged:) forControlEvents:UIControlEventValueChanged]; // method to respond to changes in the picker value
self.datePickerToolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 44)];
[self.datePickerToolbar setTintColor:[UIColor grayColor]];
UIBarButtonItem *doneBtn=[[UIBarButtonItem alloc]initWithTitle:#"Done" style:UIBarButtonItemStylePlain target:self action:#selector(dismissPicker:)];
UIBarButtonItem *space=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
[self.datePickerToolbar setItems:[NSArray arrayWithObjects:space,doneBtn, nil]];
self.TextFieldStartDt.delegate = self;
self.TextFieldStartDt.inputView = self.datePickerStart;
self.TextFieldStartDt.inputAccessoryView = self.datePickerToolbar;
self.TextFieldEndDt.delegate = self;
self.TextFieldEndDt.inputView = self.datePickerEnd;
self.TextFieldEndDt.inputAccessoryView = self.datePickerToolbar;
}
Keyboard is showing because a user has tapped a text field, not because they selected a cell. That's why allowsSelection doesn't affect it.
Move the UITextFieldDelegate handling to your UITableViewController. Then you can easily decide whether to show a date picker or not:
if date picker is hidden, show it and remember a cell which textfield is on
if date picker is already visible, check whether text field belongs to the same cell and either update date picker or do nothing (by overriding textFieldShouldBeginEditing(_:) and returning false)
I moved the UITextFieldDelegate up to the TableViewController and included following code there:
-(bool) textFieldShouldBeginEditing:(UITextField *)textField {
if (self.TableViewDiary.allowsSelection) {
return true;
} else {
CGPoint buttonPosition = [textField convertPoint:CGPointZero toView:self.TableViewDiary];
NSIndexPath *indexPath = [self.TableViewDiary indexPathForRowAtPoint:buttonPosition];
ActivityDiaryCell *cell = [self.TableViewDiary cellForRowAtIndexPath:indexPath];
if ([cell.TextFieldStartDt isFirstResponder] || [cell.TextFieldEndDt isFirstResponder]) {
return true;
}
}
return false;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
CGPoint buttonPosition = [textField convertPoint:CGPointZero toView:self.TableViewDiary];
NSIndexPath *indexPath = [self.TableViewDiary indexPathForRowAtPoint:buttonPosition];
ActivityDiaryCell *cell = [self.TableViewDiary cellForRowAtIndexPath:indexPath];
[cell setSelected:YES animated:NO];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
CGPoint buttonPosition = [textField convertPoint:CGPointZero toView:self.TableViewDiary];
NSIndexPath *indexPath = [self.TableViewDiary indexPathForRowAtPoint:buttonPosition];
ActivityDiaryCell *cell = [self.TableViewDiary cellForRowAtIndexPath:indexPath];
[cell setSelected:NO animated:YES];
}
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 UITableView to display some list. Implemented UILongPressGestureRecognizer to get calls and on this I want to display menu for delete, upload, etc actions.
Following is implementation
// Registering for long press event
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPress:)];
lpgr.delegate = self;
[self.myTable addGestureRecognizer:lpgr];
On Long Press My control comes to function
- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint p = [gestureRecognizer locationInView:self.playbackTable];
NSIndexPath *indexPath = [self.playbackTable indexPathForRowAtPoint:p];
if (indexPath == nil)
{
NSLog(#"long press on table view but not on a row");
}
else
{
NSLog(#"long press on table view at section %d row %d", indexPath.section, indexPath.row);
CGPoint p = [gestureRecognizer locationInView: self.myTable];
NSIndexPath *indexPath = [self.myTable indexPathForRowAtPoint:p];
if (indexPath != nil)
{
if([self becomeFirstResponder])
{
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:#"Delete" action:#selector(deleteFileListItem:)];
menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:delete, nil]];
[menu setTargetRect:[self.myTable rectForRowAtIndexPath:indexPath] inView:self.myTable];
[menu setMenuVisible:YES animated:YES];
}
}
}
}
}
I have also implemented following methods
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(deleteFileListItem:))
{
return YES;
}
return NO;
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
and
- (void)deleteFileListItem:(id)sender
{
// Will perform action here
}
Please let me know if anything is missing or I am doing wrong.
I've been successful when attaching a long press gesture recognizer to each cell, not the entire table view. My guess is that that's the issue here.
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.
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?