7
votes

I want to handle a tap in a UITableView not including the cells. In other words, the headers and footers.

I can add a gesture recognizer to the headers (there are no footers), but the space at the bottom of the last section does not respond to a tap.

Instead of adding the gesture recognizers above, I tried adding a gesture recognizer to the table, but it prevents tableView:didSelectRowAtIndexPath: being called.

I tried all sorts of UIGestureRecognizerDelegate calls with not much luck.

I finally got that to work by setting cancelsTouchesInView = NO on the recognizer (I think this was the secret) and implementing gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: in the delegate so that the tableView got touches and called tableView:didSelectRowAtIndexPath:

The handler has to filter out taps on the cells, but at least it works. (Also, found a bug in UITableView indexPathForRowAtPoint:point which returns invalid indexPaths sometimes.)

My question is: Is there a better way of getting the cell views to prevent touches/gestures getting through to the table? (I will post code if nobody gives me any better ideas.)

7
See my answer its working...Kirit Modi

7 Answers

4
votes

If you don't need to understand - what was clicked (footer or header), you can try the following approach.

Add gesture recognizer in viewDidLoad method.

- (void) tableViewLongPress:(UILongPressGestureRecognizer *)gestureRecognizer {
    CGPoint p = [gestureRecognizer locationInView:self.tableView];

    NSIndexPath *indexPath = [self.messageTableView indexPathForRowAtPoint:p];
    if (indexPath == nil)
        NSLog(@"long press on table view but not on a row");
    else {
        NSLog(@"long press on table view on a row");
    }
}

Attention! I haven't tested this code:)

3
votes

Here is the code that I went with.

1 Add a tap gesture recognizer to the table. Set Cancels touches in view unchecked.

2 Allow the gestures through the table.

#pragma mark - UIGestureRecognizerDelegate

/**
 * Prevents the tap gesture recognizer in the table from gobbling taps
 * and allows the table to perform tableView:didSelectRowAtIndexPath:
 * <p>
 * The gesture recognizer must have cancelsTouchesInView == NO to allow
 * the touches through to the table.
 * <p>
 * The action must check that the tap occurred outside of cells.
 *
 * @see handleTap:
 */
- (BOOL) gestureRecognizer:(UIGestureRecognizer*)recognizer
  shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)o {
    return recognizer.view == self.table;
}

3 Check the tap is not in a cell.

/**
 * Filters a tap on the table to those on the headers/footer.
 * <p>
 * Note, indexPathForRowAtPoint: has a bug and returns valid indexPaths
 * for taps in the last section header and table footer. So an extra
 * check is made to ensure that the tap was, in fact, in the cell.
 */
- (IBAction) handleTap:(UITapGestureRecognizer*)tap {
    if (UIGestureRecognizerStateEnded == tap.state) {
        CGPoint point = [tap locationInView:tap.view];
        NSIndexPath* index = [self.table indexPathForRowAtPoint:point];
        UITableViewCell* cell;

        if (index) {
            cell = [self.table cellForRowAtIndexPath:index];
            point = [tap locationInView:cell];

            if (point.y < 0 || point.y >= cell.frame.size.height) {
                index = nil;
            }
        }

        if (!index) {
            [self.view performSelector:@selector(endEditing:)
                            withObject:@(YES) afterDelay:0];
        }
    }
}
2
votes

Have you tried by adding longGesture in viewForHeaderInSection ??

If not than follow this :

-(UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{

    UILabel *headerLabel = [[UILabel alloc]init];
    headerLabel.tag = section;
    headerLabel.userInteractionEnabled = YES;
    headerLabel.backgroundColor = [UIColor greenColor];
    headerLabel.text = [NSString stringWithFormat:@"Header.%d",section];
    headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);


    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(hederClicked:)];
    tapGesture.cancelsTouchesInView = NO;
    [headerLabel addGestureRecognizer:tapGesture];

    return headerLabel;

    //return nil;
}

-(void)hederClicked:(UIGestureRecognizer*)sender
{
    UILabel *lbl = (UILabel*)sender.view;
    NSLog(@"header no : %d", lbl.tag);
}
1
votes

Adding gesture recognizers to your own section header and footer views is correct.

To repsond to touches in the area below the last row, add a footer view to the table. Add a gesture recognizer to this table footer. Make this footer view as tall as the screen.

The only downside to this is that you will lose the lines below the last row if the rows don't fill the screen. If you really want them, setup your custom table footer view to draw lines to mimic the rows.

1
votes

Add Header View in Your Tableview as below code:

Height for header view in table

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 100;
}

Also add view in header and tapgesture in this view.

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView *viewHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 100)];
    /* Create custom view to display section header... */
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, tableView.frame.size.width, 100)];
    [label setFont:[UIFont boldSystemFontOfSize:12]];

    /* Section header is in 0th index... */
    [label setText:@"Header View"];
    [viewHeader addSubview:label];
    [viewHeader setBackgroundColor:[UIColor colorWithRed:166/255.0 green:177/255.0 blue:186/255.0 alpha:1.0]];


    // Add TapGestureRecognizer

    UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];

    [recognizer setNumberOfTapsRequired:1];
    recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
    [viewHeader addGestureRecognizer:recognizer];

    return viewHeader;
}

TapGestureRecognizer mehode

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        [[[UIAlertView alloc]initWithTitle:@"Demo" message:@"Test On header Click" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show];
    }
}

Your Output:

enter image description here

1
votes

Here's the simplest most efficient implementation:

Use these UITableViewDelegate methods (iOS6+):

-(void)tableView:(UITableView *)tableView 
    willDisplayHeaderView:(UIView *)view 
    forSection:(NSInteger)

- (void)tableView:(UITableView *)tableView
    willDisplayFooterView:(UIView *)view 
    forSection:(NSInteger)section

You can use these methods to easily add a UITapGestureRecognizer to a section footer/header.

Ex:

    - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
    { 
        UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionTapped:)];

        [view addGestureRecognizer:recognizer];

   }

    - (IBAction)sectionTapped:(UITapGestureRecognizer *)recognizer {
     NSLog(@"Tapped");
    }
0
votes

As proposed on this post, you could override UITableView and the UIView hitTest:withEvent method to detect if a click corresponds to a known NSIndexPath:

private class CustomDetectTableView: UITableView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        if self.indexPathForRow(at: point) == nil {
           // if code reaches this part, the user did click 
           // outside any known index paths.
           // Delegation or `NSNotificationCenter` could be used 
        }

        return super.hitTest(point, with: event)
    }
}