Imitate Facebook hide/show expanding/contracting Navigation Bar
The solution given by @peerless is a great start, but it only kicks off an animation whenever dragging begins, without considering the speed of the scroll. This results in a choppier experience than you get in the Facebook app. To match Facebook's behavior, we need to:
- hide/show the navbar at a rate that is proportional to the rate of the drag
- kick off an animation to completely hide the bar if scrolling stops when the bar is partially hidden
- fade the navbar's items as the bar shrinks.
First, you'll need the following property:
@property (nonatomic) CGFloat previousScrollViewYOffset;
And here are the UIScrollViewDelegate
methods:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ CGRect frame = self.navigationController.navigationBar.frame; CGFloat size = frame.size.height - 21; CGFloat framePercentageHidden = ((20 - frame.origin.y) / (frame.size.height - 1)); CGFloat scrollOffset = scrollView.contentOffset.y; CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset; CGFloat scrollHeight = scrollView.frame.size.height; CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom; if (scrollOffset <= -scrollView.contentInset.top) { frame.origin.y = 20; } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) { frame.origin.y = -size; } else { frame.origin.y = MIN(20, MAX(-size, frame.origin.y - scrollDiff)); } [self.navigationController.navigationBar setFrame:frame]; [self updateBarButtonItems:(1 - framePercentageHidden)]; self.previousScrollViewYOffset = scrollOffset;}- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ [self stoppedScrolling];}- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { [self stoppedScrolling]; }}
You'll also need these helper methods:
- (void)stoppedScrolling{ CGRect frame = self.navigationController.navigationBar.frame; if (frame.origin.y < 20) { [self animateNavBarTo:-(frame.size.height - 21)]; }}- (void)updateBarButtonItems:(CGFloat)alpha{ [self.navigationItem.leftBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) { item.customView.alpha = alpha; }]; [self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) { item.customView.alpha = alpha; }]; self.navigationItem.titleView.alpha = alpha; self.navigationController.navigationBar.tintColor = [self.navigationController.navigationBar.tintColor colorWithAlphaComponent:alpha];}- (void)animateNavBarTo:(CGFloat)y{ [UIView animateWithDuration:0.2 animations:^{ CGRect frame = self.navigationController.navigationBar.frame; CGFloat alpha = (frame.origin.y >= y ? 0 : 1); frame.origin.y = y; [self.navigationController.navigationBar setFrame:frame]; [self updateBarButtonItems:alpha]; }];}
For a slightly different behavior, replace the line that re-positions the bar when scrolling (the else
block in scrollViewDidScroll
) with this one:
frame.origin.y = MIN(20, MAX(-size, frame.origin.y - (frame.size.height * (scrollDiff / scrollHeight))));
This positions the bar based on the last scroll percentage, instead of an absolute amount, which results in a slower fade. The original behavior is more Facebook-like, but I like this one, too.
Note: This solution is iOS 7+ only. Be sure to add the necessary checks if you're supporting older versions of iOS.
EDIT: Only for iOS 8 and above.
You can try use
self.navigationController.hidesBarsOnSwipe = YES;
Works for me.
If your coding in swift you have to use this way (from https://stackoverflow.com/a/27662702/2283308)
navigationController?.hidesBarsOnSwipe = true
Here is one more implementation: TLYShyNavBar v1.0.0 released!
I decided to make my own after trying the solutions provided, and to me, they were either performing poorly, had a a high barrier of entry and boiler plate code, or lacked the extension view beneath the navbar. To use this component, all you have to do is:
self.shyNavBarManager.scrollView = self.scrollView;
Oh, and it is battle tested in our own app.