Episode #110

Swipe to Reveal Redux

12 minutes
Published on March 6, 2014

This video is only available to subscribers. Get access to this video and 573 others.

In this episode we fix the problem of a scroll view blocking touches to a UITableViewCell by forwarding touch events from the scroll view and onto a delegate, which manages the cell's highlighted state properly.

Errata

  • In the episode I create a strong reference to the tableView from a cell. In this way, I unintentionally create a retain cycle. Since UITableView retains its cells, a weak pointer is appropriate here. Thanks to Scott Williams for pointing this out.

Episode Links

Catching Taps in a Scroll View

Here we subclass UIScrollView into our own class, BSTapScrollView. It implements these methods:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];

    if (!self.dragging) {
        [self.tapDelegate tapScrollView:self touchesBegan:touches withEvent:event];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];

    [self.tapDelegate tapScrollView:self touchesCancelled:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    [self.tapDelegate tapScrollView:self touchesCancelled:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"Touch on scroll view ended");
    if (!self.dragging) {
        [self.tapDelegate tapScrollView:self touchesEnded:touches withEvent:event];
    } else {
        [super touchesEnded:touches withEvent:event];
    }
}

Note the use of a delegate that handles the tap events. The delegate is defined as:


@protocol BSTapScrollViewDelegate <NSObject>

@optional

- (void)tapScrollView:(BSTapScrollView *)scrollView touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)tapScrollView:(BSTapScrollView *)scrollView touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)tapScrollView:(BSTapScrollView *)scrollView touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

@end

To get this to work, we'll set our cell up as the tapDelegate for our scroll view:

@interface MailMessageCell () <BSTapScrollViewDelegate> ...

And in awakeFromNib...

self.scrollView.tapDelegate = self;

Managing the Highlighted State

#pragma mark - BSTapScrollViewDelegate

- (void)tapScrollView:(BSTapScrollView *)scrollView touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"Setting highlighted...");

    if (_isOpen) {
    } else {
        [self setHighlighted:YES animated:YES];
    }
}

- (void)tapScrollView:(BSTapScrollView *)scrollView touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"Setting normal...");
    [self setHighlighted:NO animated:NO];
}

- (void)tapScrollView:(BSTapScrollView *)scrollView touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (_isOpen) {
        return;
    }
    NSLog(@"did select....");
    NSIndexPath *indexPath = [self.tableView indexPathForCell:self];
    [self setHighlighted:NO animated:NO];
    [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    [self.tableView.delegate tableView:self.tableView didSelectRowAtIndexPath:indexPath];

    [[NSNotificationCenter defaultCenter] postNotificationName:RevealCellDidOpenNotification
                                                        object:self];
}