Imitate iOS 7 Facebook hide/show expanding/contracting Navigation Bar

In the new iOS7 Facebook iPhone app, when the user scrolls up the navigationBar gradually hides itself to a point where it completely vanishes. Then when the user scrolls down the navigationBar gradually shows itself.

How would you implement this behavior yourself? I am aware of the following solution but it disappears right away and it isn’t tied to the speed of the user’s scroll gesture at all.

[navigationController setNavigationBarHidden: YES animated:YES];

I hope this isn’t a duplicate as I’m not sure how best to describe the “expanding/contracting” behavior. Cheers and thanks in advance!

Imitate iOS 7 Safari Nav-Bar

In iOS7 the navigationBar in Safari automatically shrinks when scrolling. So does the navigationBar in Facebook, even to a point where it completely vanishes. How would you implement this behavior you

iOS 7 navigation bar height

Is it possible to change navigation bar height in iOS 7? why I need navigation bar: to have a transparent navigation bar with the right positions of others elements on my view without any problems; I

IOS 7 Navigation bar overlapping Navigation bar

I had developed an app earlier for ios 4 and above, in which I had used 2 navigation bars on top. 1 was a global bar common for all the views and the other changed with the views. In IOS 7 the main n

ios Navigation bar tint color like facebook app

I need a navigation bar tint color like facebook’s navigation bar(in iOS 7) I picked the color of facebook App’s and set it by [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithHexString

iOS 7 Navigation Bar Hiding Content

I have an app that was developed in iOS 6. But now in iOS 7 or even my app compiled for iOS 6, but running on an iOS 7 device the top navigation bar (the new giant one in iOS 7) my content is hidden.

Set navigation bar image in iOS 7

I want to convert my current project from iOS 6 to iOS 7. In iOS 6 my project is working fine, but in iOS 7 navigation bar image is not showing properly. I used this code snippet for iOS 6, UIImage *i

Navigation bar and Status bar issue in iOS 7

I have created an application using XIB without Status Bar before for iOS7, now i need to add Status bar on my app and Status bar background color should same as Navigation bar background color. So i

Custom Navigation Bar image – iOS 7

In iOS 7 , I have used custom navigation bar here . But the problem is How can I set the image as it is 320*64 but the navigation item iOS is allowing me is 44pixel .. I am able to add the image on na

iOS 7 navigation bar after reopening app

I am having a problem with navigation bar in iOS 7. It looks goods when I launch my app. But when I close app and reopen navigation bar looks weird. I don’t know how to fix that. Before After

ios 7 navigation bar too small

I am trying to update my iPhone app to iOS7. I am having major issues with the navigation bar. I would like it to have the new navigation bar. As you can see from my image it has squashed the title

Answers

One way that I’ve accomplished this is the following.

Register your view controller to be the UIScrollViewDelegate of your UITableView for example.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;

From within de UIScrollViewDelegate methods you can get the new contentOffset and translate your UINavigationBar up or down accordingly.

Setting the alpha of the subviews can also be done based on some threshold values and factors you can set and compute.

Hope it helps!

I have some kind of a quick and dirty solution for that. Haven’t made any in-depth testing but here’s the idea:

That property will keep all the items in the navbar for my UITableViewController class

@property (strong, nonatomic) NSArray *navBarItems;

In the same UITableViewController class I have:

-(void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){
        return;
    }

    CGRect frame = self.navigationController.navigationBar.frame;
    frame.origin.y = 20;

    if(self.navBarItems.count > 0){
        [self.navigationController.navigationBar setItems:self.navBarItems];
    }

    [self.navigationController.navigationBar setFrame:frame];
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){
        return;
    }

    CGRect frame = self.navigationController.navigationBar.frame;
    CGFloat size = frame.size.height - 21;

    if([scrollView.panGestureRecognizer translationInView:self.view].y < 0)
    {
        frame.origin.y = -size;

        if(self.navigationController.navigationBar.items.count > 0){
            self.navBarItems = [self.navigationController.navigationBar.items copy];
            [self.navigationController.navigationBar setItems:nil];
        }
    }
    else if([scrollView.panGestureRecognizer translationInView:self.view].y > 0)
    {
        frame.origin.y = 20;

        if(self.navBarItems.count > 0){
            [self.navigationController.navigationBar setItems:self.navBarItems];
        }
    }

    [UIView beginAnimations:@"toggleNavBar" context:nil];
    [UIView setAnimationDuration:0.2];
    [self.navigationController.navigationBar setFrame:frame];
    [UIView commitAnimations];
}

That’s only for ios >= 7, it’s ugly I know but a quick way to achieve this. Any comments/suggestions are welcome 🙂

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

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.

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.

In addition to Iwburk’s answer I added the following to fix the alpha issue on non custom navigation bars and to reset the navigation bar in the viewWillDisappear method:

- (void)updateBarButtonItems:(CGFloat)alpha
{
    for (UIView *view in self.navigationController.navigationBar.subviews) {
        NSString *className = NSStringFromClass([view class]);

        if ( ![className isEqualToString:@"_UINavigationBarBackground"] ) {
            view.alpha = alpha;
        }
    }
}

- (void)resetNavigationBar {
    CGRect frame = self.navigationController.navigationBar.frame;
    frame.origin.y = 20;
    [self.navigationController.navigationBar setFrame:frame];
    [self updateBarButtonItems:1.0f];
}

Here is my implementation: SherginScrollableNavigationBar.

Here is one more implementation: TLYShyNavBar

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 = self.scrollView;

Oh, and it is battle tested in our own app.

Imitate iOS 7 Facebook hide/show expanding/contracting Navigation Bar

I tried implementing GTScrollNavigationBar but my app required me to modify auto layout constraints. I decided to put an example of my implementation up on GitHub in case anyone else has to do this with auto layout. The other issue I had with most of the other implementations is that people don’t set the bounds of the scroll view to avoid the parallax scrolling effect that you create while you scroll and adjust the size of the scrollview simultaneously.

Check out JSCollapsingNavBarViewController if you need to do this with auto layout. I’ve included two versions, one with the nav bar only and another with a sub-bar below the nav bar which collapses before collapsing the nav bar.

I was trying to emulate this behavior in a situation where I needed a customized header sitting about a UITableView. I rolled my own “navigation” bar because this sits below a bunch of other stuff on the page and I wanted the section headers to follow the default “docking” behavior. I think I found a pretty clever and succinct way to adjust a UITableView/UIScrollView together with another object in a style similar to that seen in the Facebook/Instagram/Chrome/etc. apps.

In my .xib file, I have my components loaded into a freeform view: http://imgur.com/0z9yebJ (sorry, don’t have the rep to inline images)

Notice that, in the left sidebar, the table is ordered behind the main header view. You can’t tell from the screenshot, but it also has the same y position as the main header view. Since it extends out of sight, the contentInset property on the UITableView set to 76 (the height of the main header view).

To make the main header view slide up in unison with the UIScrollView, I use the UIScrollViewDelegate’s scrollViewDidScroll methods to perform some calculations and change the UIScrollView’s contentInset as well as the main header view’s frame.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    UIEdgeInsets insets = scrollView.contentInset;
    //tableViewInsetDelta and tableViewOriginalInsetValue are NSInteger variables that I set to 0 and 76, respectively, in viewDidLoad
    tableViewInsetDelta = tableViewOriginalInsetValue + scrollView.contentOffset.y;
    insets.top = tableViewOriginalInsetValue - tableViewInsetDelta;

    if (scrollView.contentOffset.y > -76 && scrollView.contentOffset.y < 0) {
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44 - tableViewInsetDelta, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    } else if (scrollView.contentOffset.y > 0) {
        insets.top = 0;
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, -32, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    } else if (scrollView.contentOffset.y < -76) {
        insets.top = 76;
        [scrollView setContentInset:insets];
        self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height);
    }
}

The first if statement does most of the heavy lifting, but I had to include the other two to handle situations where the user is dragging forcefully and the initial contentOffset values sent to scrollViewDidScroll are outside of the range of the first if statement.

Ultimately, this is working really well for me. I hate loading up my projects with a bunch of bloated subclasses. I can’t speak to whether this is the best solution performance-wise (I’ve always been hesitant to put any code in scrollViewDidScroll since it gets called all the time), but the code footprint is the smallest I’ve seen in any solution for this problem and it doesn’t involve nesting a UITableView in a UIScrollView (Apple advises against this in the documentation and touch events end up a bit funky on the UITableView). Hope this helps someone!

iOS8 includes properties to get the navigation bar hiding for free. There is a WWDC video that demonstrates it, search for “View Controller Advancements in iOS 8”.

Example:

class QuotesTableViewController: UITableViewController {

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    navigationController?.hidesBarsOnSwipe = true
}

}

Other properties:

class UINavigationController : UIViewController {

//... truncated

/// When the keyboard appears, the navigation controller's navigationBar toolbar will be hidden. The bars will remain hidden when the keyboard dismisses, but a tap in the content area will show them.
@availability(iOS, introduced=8.0)
var hidesBarsWhenKeyboardAppears: Bool
/// When the user swipes, the navigation controller's navigationBar & toolbar will be hidden (on a swipe up) or shown (on a swipe down). The toolbar only participates if it has items.
@availability(iOS, introduced=8.0)
var hidesBarsOnSwipe: Bool
/// The gesture recognizer that triggers if the bars will hide or show due to a swipe. Do not change the delegate or attempt to replace this gesture by overriding this method.
@availability(iOS, introduced=8.0)
var barHideOnSwipeGestureRecognizer: UIPanGestureRecognizer { get }
/// When the UINavigationController's vertical size class is compact, hide the UINavigationBar and UIToolbar. Unhandled taps in the regions that would normally be occupied by these bars will reveal the bars.
@availability(iOS, introduced=8.0)
var hidesBarsWhenVerticallyCompact: Bool
/// When the user taps, the navigation controller's navigationBar & toolbar will be hidden or shown, depending on the hidden state of the navigationBar. The toolbar will only be shown if it has items to display.
@availability(iOS, introduced=8.0)
var hidesBarsOnTap: Bool
/// The gesture recognizer used to recognize if the bars will hide or show due to a tap in content. Do not change the delegate or attempt to replace this gesture by overriding this method.
@availability(iOS, introduced=8.0)
unowned(unsafe) var barHideOnTapGestureRecognizer: UITapGestureRecognizer { get }

}

Found via http://natashatherobot.com/navigation-bar-interactions-ios8/

You can try use

self.navigationController.hidesBarsOnSwipe = YES;

Works for me.

I was looking for a solution that allowed for any style and any behavior. You’ll notice that bar condensing behavior is different in many different apps. And of course, the way the bar looks is totally different between apps.

I created a solution for this issue with https://github.com/bryankeller/BLKFlexibleHeightBar/

You can definine your own behavior rules to control how and when the bar shrinks and grows, and you can define exactly how you want the bar’s subviews to react to the bar condensing or growing.

Have a look at my project if you want a lot of flexibility to make whatever kind of header bar you can think up.

i tried it with this way, i hope it will help. just implement the code in delegate method and set to to the desired view/subview

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{ 
            CGRect frame=self.view.frame;
            CGRect resultFrame=CGRectZero;
            if(scrollView.contentOffset.y==0 || scrollView.contentOffset.y<0){
                self.lastContentOffset=0;
                self.offset=0;
                resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                [self showHide:YES withFrame:resultFrame];
            }else if (self.lastContentOffset > scrollView.contentOffset.y){
                NSNumber *temp=[NSNumber numberWithDouble:self.lastContentOffset-scrollView.contentOffset.y];
                if(temp.intValue>40 || self.offset.intValue<temp.intValue){
                    self.offset=[NSNumber numberWithInt:0];
                    resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                    [self showHide:YES withFrame:resultFrame];
                }else{
                    if(temp.intValue>0){
                        self.offset=[NSNumber numberWithInt:self.offset.intValue-temp.intValue];
                        resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                        [self showHide:YES withFrame:resultFrame];
                    }
                }
            }else if (self.lastContentOffset < scrollView.contentOffset.y){
                NSNumber *temp=[NSNumber numberWithDouble:scrollView.contentOffset.y-self.lastContentOffset];
                if(self.offset.intValue>40 || (self.offset.intValue+temp.intValue)>40){
                    self.offset=[NSNumber numberWithInt:40];
    // Pass the resultFrame
                    [self showHide:NO withFrame:resultFrame];
                }else{
                    self.offset=[NSNumber numberWithInt:self.offset.intValue+temp.intValue];
                    resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue);
    // Pass the resultFrame
                    [self showHide:YES withFrame:resultFrame];
                }
            }
            self.lastContentOffset = scrollView.contentOffset.y;

        }

-(void)showHide:(Boolean)boolView withFrame:(CGRect)frame{
               if(showSRPFilter){
                        //Assign value of "frame"to any view on which you wan to to perform animation
                }else{
                       //Assign value of "frame"to any view on which you wan to to perform animation
                }
        }

All of these approaches seem overly complicated… So naturally, I built my own:

class ViewController: UIViewController, UIScrollViewDelegate {
    var originalNavbarHeight:CGFloat = 0.0
    var minimumNavbarHeight:CGFloat = 0
    weak var scrollView:UIScrollView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // setup delegates 
        scrollView.delegate = self
        // save the original nav bar height
        originalNavbarHeight = navigationController!.navigationBar.height
    }


    func scrollViewDidScroll(scrollView: UIScrollView) {
        // will relayout subviews
        view.setNeedsLayout() // calls viewDidLayoutSubviews
    }

    override func viewDidLayoutSubviews() {
        var percentageScrolled = min(scrollView.contentOffset.y / originalNavbarHeight, 1)
        navigationController?.navigationBar.height = min(max((1 - percentageScrolled) * originalNavbarHeight, minimumNavbarHeight), originalNavbarHeight)
        // re-position and scale scrollview
        scrollView.y = navigationController!.navigationBar.height + UIApplication.sharedApplication().statusBarFrame.height
        scrollView.height = view.height - scrollView.y
    }

    override func viewWillDisappear(animated: Bool) {
        navigationController?.navigationBar.height = originalNavbarHeight
    }

}

Please try this solution of mine and let me know why this ain’t as good as the previous answers.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    if (fabs(velocity.y) > 1)
        [self hideTopBar:(velocity.y > 0)];
}

- (void)hideTopBar:(BOOL)hide
{
    [self.navigationController setNavigationBarHidden:hide animated:YES];
    [[UIApplication sharedApplication] setStatusBarHidden:hide withAnimation:UIStatusBarAnimationSlide];
}

An extension of @Iwburk ‘s answer… Instead of changing the origin of the navigation bar, I needed to expand/shrink the size of the navigation bar.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect frame = self.previousRect; // a property set in the init method to hold the initial size of the uinavigationbar
    CGFloat size = frame.size.height;
    CGFloat framePercentageHidden = ((MINIMUMNAVBARHEIGHT - 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 = -MINIMUMNAVBARHEIGHT;
    } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
        frame.origin.y = -size;
    } else {
        frame.origin.y = MIN(-MINIMUMNAVBARHEIGHT, MAX(-size, frame.origin.y - scrollDiff));
    }

    self.previousRect = CGRectMake(0, frame.origin.y, self.jsExtendedBarView.frame.size.width, 155);
    self.layoutConstraintExtendedViewHeight.constant = MAXIMUMNAVBARHEIGHT + frame.origin.y + MINIMUMNAVBARHEIGHT;
    [self updateBarButtonItems:(1 - framePercentageHidden)];
    self.previousScrollViewYOffset = scrollOffset;
}

It doesn’t work with the stoppedScrolling method yet, ill post an update when I have it

This works for iOS 8 and above and ensures that the status bar still retains its background

self.navigationController.hidesBarsOnSwipe = YES;
CGRect statuBarFrame = [UIApplication sharedApplication].statusBarFrame;
UIView *statusbarBg = [[UIView alloc] initWithFrame:statuBarFrame];
statusbarBg.backgroundColor = [UIColor blackColor];
[self.navigationController.view addSubview:statusbarBg];

And if you want to show the nav bar when you tap on the status bar you can do this:

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
     self.navigationController.navigationBarHidden = NO;
}

HidingNavigationBar a great project that hides the Navigation Bar and the Tab Bar if you want.

HidingNavigationBar supports hiding/showing of the following view elements:

UINavigationBar

UINavigationBar and an extension UIView

UINavigationBar and a UIToolbar

UINavigationBar and a UITabBar

https://github.com/tristanhimmelman/HidingNavigationBar