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.
You can have a look at my GTScrollNavigationBar. I have subclassed UINavigationBar to make it scroll based on the scrolling of a UIScrollView.
Note: If you have an OPAQUE navigation bar, the scrollview must EXPAND as the navigation bar gets HIDDEN. This is exactly what GTScrollNavigationBar does. (Just as in for example Safari on iOS.)