29

I have UIViewControllerand UITableView as child in the view, what I want to do is when I touch any row I am displaying a view at bottom. I want to hide that view if the user touch any where else then rows or the bottomView.

The problem is when I click on UITableView it doesn't fires touchesEnded event.

Now how can I detect touch on UITableView and distinguish it with row selection event.

Thanks.

Ahmad Kayyali
  • 8,233
  • 13
  • 49
  • 83
Kapil Choubisa
  • 5,152
  • 9
  • 65
  • 100
  • Im not sure I understand. Do you mean that you have a UIView (viewA) which holds a UITableView, and another UIView (viewB) (at the bottom). If the user touches anywhere other than a UiTableViewCell, or viewB, then you wish to hide viewB. Otherwise, show viewB. is that correct? – Jason Cragun May 01 '11 at 14:19
  • Yes exactly this I want to do.. And if user touch on UITableViewCell I want to show the viewB. That I can achive via didRowSelectedAtIndexPath: – Kapil Choubisa May 05 '11 at 09:55
  • So are you asking.. "how do you capture the touch events that happen on a UITableView, but not in a cell?" – Jason Cragun May 05 '11 at 15:34
  • Yes ofcourse.. I need to detect the touch of UITableView not cell – Kapil Choubisa May 06 '11 at 05:36

7 Answers7

54

No need to subclass anything, you can add a UITapGestureRecognizer to the UITableView and absorb the gesture or not depending on your criteria.

In your viewDidLoad:

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapOnTableView:)];
[self.myTableView addGestureRecognizer:tap];

Then, implement your action like this for the criteria:

-(void) didTapOnTableView:(UIGestureRecognizer*) recognizer {
    CGPoint tapLocation = [recognizer locationInView:self.myTableView];
    NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint:tapLocation];

    if (indexPath) { //we are in a tableview cell, let the gesture be handled by the view
        recognizer.cancelsTouchesInView = NO;
    } else { // anywhere else, do what is needed for your case
        [self.navigationController popViewControllerAnimated:YES];
    }
}

And note that if you just want to simply pick up clicks anywhere on the table, but not on any buttons in cell rows, you need only use the first code fragment above. A typical example is when you have a UITableView and there is also a UISearchBar. You want to eliminate the search bar when the user clicks, scrolls, etc the table view. Code example...

-(void)viewDidLoad {
    [super viewDidLoad];
    etc ...

    [self _prepareTable];
}
-(void)_prepareTable {
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.tableView.allowsSelection = NO;
    etc...

    UITapGestureRecognizer *anyTouch =
        [[UITapGestureRecognizer alloc]
         initWithTarget:self action:@selector(tableTap)];
    [self.tableView addGestureRecognizer:anyTouch];
}
// Always drop the keyboard when the user taps on the table:
// This will correctly NOT affect any buttons in cell rows:
-(void)tableTap {
    [self.searchBar resignFirstResponder];
}
// You probably also want to drop the keyboard when the user is
// scrolling around looking at the table. If so:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [self.searchBar resignFirstResponder];
}
// Finally you may or may not want to drop the keyboard when
// a button in one cell row is clicked. If so:
-(void)clickedSomeCellButton... {
    [self.searchBar resignFirstResponder];
    ...
}

Hope it helps someone.

David
  • 9,635
  • 5
  • 62
  • 68
  • 1
    I prefer to call resignFirstResponder inside the scrollViewWillBeginDragging:(UIScrollView *)scrollView method instead of scrollViewDidScroll. Because if user will scroll and then click on searchBar during the scroll deceleration - keyboard will appear on the screen for a second and then it will hide which is not an appropriate behaviour. With scrollViewWillBeginDragging method this problem is solved. – Werewolf Oct 20 '16 at 09:04
14

You should forward the touch event to the view's controller. Subclass your tableview control and then override the method:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];  //let the tableview handle cell selection 
    [self.nextResponder touchesBegan:touches withEvent:event]; // give the controller a chance for handling touch events 
}

then , you can do what you want in the controller's touch methods.

Patrick
  • 6,495
  • 6
  • 51
  • 78
longway
  • 181
  • 2
  • 6
  • Thanks @longway but I am not getting touchsBegan called than how can I forwar it to view's controller??? The question is all about how to call touchesBegan event when I touch on tableView :) – Kapil Choubisa Jul 21 '12 at 11:26
  • you subclass UItableView, override the touch handle methods. Then you create your table use your custom tableview class – longway Jul 21 '12 at 12:18
  • Hi, I've tried ur solution which of course works to detect topuch on tableview and pass to its corresponding superview. Nut now I have problem that UITableview delegate method, i.e. didSelectRowAtIndexPath is not getting called. Ultimately what I want to so is, first detect touch on tableview pass it superview do some action and then execute didSelectRowAtIndexPath. Can you help me in this?? – Kavya Indi Aug 02 '13 at 10:54
8

I just stumbled onto what may be a solution for your problem. Use this code when you create your table view:

tableView.canCancelContentTouches = NO;

Without setting this to NO, the touch events are cancelled as soon as there is even a slight bit of vertical movement in your table view (if you put NSLog statements in your code, you'll see that touchesCancelled is called as soon as the table starts scrolling vertically).

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • 2
    your solution doesn't work. I tried you solution and Used all touch event with log including `touchesCancelled`. In `viewDidLoad` I set `[tblTemp setCanCancelContentTouches:NO];`. Non of my touch method is being called. – Kapil Choubisa Jul 23 '12 at 04:30
6

I was facing the problem since a long time and didn't got any working solution. Finally I choose to go with a alternative. I know technically this is not the solution but this may help someone looking for the same for sure.

In my case I want to select a row that will show some option after that I touch anywhere on table or View I want to hide those options or do any task except the row selected previously for that I did following:

  1. Set touch events for the view. This will do the task when you touch anywhere on the view except the table view.

  2. TableView's didSelectRowAtIndexPath do following

     - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
        if(indexPath.row != previousSelectedRow && previousSelectedRow != -1) {
            // hide options or whatever you want to do
            previousSelectedRow = -1;
        }
        else {
            // show your options or other things
            previousSelectedRow = indexPath.row;
        }
     }
    

I know that this is older post and not a good technical solution but this worked for me. I am posting this answer because this may help someone for sure.

Note: The code written here may have spell mistakes because directly typed here. :)

Kapil Choubisa
  • 5,152
  • 9
  • 65
  • 100
3

Try this methods:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{

}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{

}

Use scrollViewDidEndDragging like alternative of touchesEnded. Hope it helps.

Nuzhdin Vladimir
  • 1,714
  • 18
  • 36
3

To receive touch events on the UITableView use:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  //<my stuff>

  [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
   //<my stuff>

   [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
  //<my stuff>

  [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
   //<my stuff>
   [super touchesCancelled:touches withEvent:event];
}
Maciej Gad
  • 1,701
  • 16
  • 21
Ahmad Kayyali
  • 8,233
  • 13
  • 49
  • 83
-3

In your controller class declare a method which removes the bottom view. Something like this:

-(IBAction)bottomViewRemove:(id)sender {

[bottomView removeFromSuperview];

}

In Interface Builder, select your view and in the identity inspector in the custom class section, change the class from UIView to UIControl. After that go to the connections inspector and connect the TouchUpInside event to the method declared above. Hope this helps.

HG's
  • 818
  • 6
  • 22