Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Imitate Facebook hide/show expanding/contracting Navigation Bar

People also ask

What is the top bar on Facebook called?

In case you're wondering, the navigation bar appears at the top of your Facebook app on Android, and at the bottom on iOS. It's your main way to switch between main features that can also be found through the “burger menu” on the right.

What is the bottom bar on Facebook called?

The navigation bar, which spans the bottom of the screen on the iOS app and the top on Android, previously showed every user in the same country the same five icons. For example, in the US, it was the News Feed, notifications, menu, Watch, and Marketplace.


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.)