In this episode I customize UITableViewCell to provide swipe to reveal behavior, similar to Mail.app. We use UIScrollView's delegate methods to ensure that we never land mid-way through the swipe, raise notifications to make sure only one cell is open at a time, and we use a help app called Reveal to assist us in visualizing the view hierarchy.
Episode Links Source Code Reveal - I used the trial here to help visualize the view hierarchy Creating our custom buttons - (void)awakeFromNib { self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO; self.moreButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.moreButton.titleLabel.font = [UIFont systemFontOfSize:14.0]; self.moreButton.backgroundColor = [UIColor colorWithWhite:0.76 alpha:1.0]; self.moreButton.frame = CGRectMake(0, 0, kRevealWidth / 2.0, self.contentView.frame.size.height); [self.moreButton setTitle:@"More..." forState:UIControlStateNormal]; self.deleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.deleteButton.titleLabel.font = [UIFont systemFontOfSize:14.0]; self.deleteButton.backgroundColor = [UIColor redColor]; self.deleteButton.frame = CGRectMake(self.moreButton.frame.size.width, 0, kRevealWidth / 2.0, self.contentView.frame.size.height); [self.deleteButton setTitle:@"Delete" forState:UIControlStateNormal]; self.buttonContainerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kRevealWidth, self.deleteButton.frame.size.height)]; [self.buttonContainerView addSubview:self.moreButton]; [self.buttonContainerView addSubview:self.deleteButton]; [self.scrollView insertSubview:self.buttonContainerView belowSubview:self.innerContentView]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOpen:) name:RevealCellDidOpenNotification object:nil]; } Next we need to reposition our buttons to the right side, accounting for scroll offset. Positioning the buttons - (void)layoutSubviews { [super layoutSubviews]; self.contentView.frame = self.bounds; self.scrollView.contentSize = CGSizeMake(self.contentView.frame.size.width + kRevealWidth, self.scrollView.frame.size.height); [self repositionButtons]; } - (void)repositionButtons { CGRect frame = self.buttonContainerView.frame; frame.origin.x = self.contentView.frame.size.width - kRevealWidth + self.scrollView.contentOffset.x; self.buttonContainerView.frame = frame; } Handling the scroll - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // make sure the buttons are fixed in place [self repositionButtons]; // don't allow scrolling to the right if (scrollView.contentOffset.x < 0) { scrollView.contentOffset = CGPointZero; } // if we're open, we need to close the others if (scrollView.contentOffset.x >= kRevealWidth) { _isOpen = YES; [[NSNotificationCenter defaultCenter] postNotificationName:RevealCellDidOpenNotification object:self]; } else { _isOpen = NO; } } Handling the open notification - (void)onOpen:(NSNotification *)notification { if (notification.object != self) { if (_isOpen) { dispatch_async(dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.25 animations:^{ self.scrollView.contentOffset = CGPointZero; }]; }); } } } Snapping to open or closed We don't want our scroll to land in between the open & closed states. We can do this by adjusting where the scroll will end when we finish dragging. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { if (velocity.x > 0) { (*targetContentOffset).x = kRevealWidth; } else { (*targetContentOffset).x = 0; } }