In this episode I implement a fast scrolling "nub" to assist with scrolling through table views with many entries. The technique was lifted from the Dropbox app and I build a quick prototype of how it works.
Episode Links Episode Source Code Hiding / Showing the Nub First we have a couple of helper methods we'll use: - (void)animateNumbAlpha:(CGFloat)alpha { [UIView animateWithDuration:0.5 animations:^{ self.scrollingNub.alpha = alpha; }]; } - (void)hideNubAfterDelay { double delayInSeconds = 0.75; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if (!self.scrubbing && ![self.tableView isDecelerating]) { [self animateNumbAlpha:0]; } }); } We star the nub out hidden, so we want to show it when we scroll: - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (self.scrollingNub.alpha == 0) { [self animateNumbAlpha:1]; } } Then we want it to hide when we stop dragging, and when the scroll view comes to a rest: - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self hideNubAfterDelay]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self hideNubAfterDelay]; } This solution works okay, but as you saw in the video it needs a bit more work. Updating the nub's position as you scroll In our scroll event we'll reposition the nub. The only part that changes is the y value, so we extract that into a method. - (CGFloat)nubY { CGFloat minY = self.tableView.contentInset.top + 10; CGFloat maxY = self.view.bounds.size.height - self.scrollingNub.bounds.size.height - 10; return [self percentageThroughContent] * (maxY - minY ) + minY; } The y value here is dependent on the scroll position, which itself is described in terms of the percent through the content. We calculate that in the following method: - (CGFloat)percentageThroughContent { CGFloat bottomY = self.tableView.contentSize.height - self.tableView.bounds.size.height; if (bottomY == 0) { return 0; } return self.tableView.contentOffset.y / bottomY; } With this we can see the nub moving as we scroll. Dragging the nub We need to attach a PanGestureRecognizer to the nub so we can grab it with our finger. UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onNubScrub:)]; [self.scrollingNub addGestureRecognizer:pan]; if (pan.state == UIGestureRecognizerStateBegan) { self.scrollingNub.backgroundColor = [UIColor blueColor]; self.scrubbing = YES; } else if (pan.state != UIGestureRecognizerStateChanged) { self.scrollingNub.backgroundColor = [UIColor greenColor]; self.scrubbing = NO; [self hideNubAfterDelay]; } CGFloat translation = [pan translationInView:self.view].y; CGFloat minY = self.tableView.contentInset.top + 10; CGFloat maxY = self.view.bounds.size.height - self.scrollingNub.bounds.size.height - 10; CGRect rect = self.scrollingNub.frame; rect.origin.y += translation; rect.origin.y = MAX(rect.origin.y, minY); rect.origin.y = MIN(rect.origin.y, maxY); self.scrollingNub.frame = rect; // y = p * (maxY - minY) + minY // y - minY // --------- = p // (maxY - minY) CGFloat percent = (rect.origin.y - minY ) / (maxY - minY ); percent = MIN(percent, 1); percent = MAX(percent, 0); CGFloat minOffset = - self.tableView.contentInset.top; CGFloat maxOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; CGFloat scrollOffset = percent * (maxOffset - minOffset) + minOffset; CGPoint offset = CGPointMake(0, scrollOffset); [self.tableView setContentOffset:offset]; [pan setTranslation:CGPointZero inView:self.view]; } The equation used here to calculate the scroll percentage is derived from the equation we used to set the nub y position. There's certainly some duplication here that could be refactored, but for now the math is easy enough to understand. Now we have a working prototype. The next step here would be to figure out the proper way to extract this code into a component that we can apply more easily, rather than dumping all of this code in a view controller.