In this tutorial you will learn to create a toolbar or tabbar style view that can be animated on and off the screen. By using a toolbar that can be collapsed, you can give your users the option of more screen real-estate. We will create a UIView that can hold a couple buttons and can be expanded or collapsed when a button is touched. The user can also drag it open or closed with a pan gesture.

toolbar expanding and collapsing animation

This tutorial requires some iOS experience to understand what is happening. When I wrote this I used Xcode 4.6.1 targeting iOS 6.1, but I’ve tested it back to at least iOS 5.0. As a side note, I write in the style of Modern Objective-C, which means things like not synthesizing properties or forward declaring methods.

Getting Started

Start by creating a new Xcode project with the Single View Application template. Use storyboards and Arc. You don’t have to use a storyboard to make something like this, but in this tutorial I use one. I also have disabled auto-layout on the storyboard.

Lay out the view

Let’s start this one by getting the storyboard all set. We can just use the ViewController that was created for us because this tutorial will only use a single view. Laying out the view might be a little complicated to explain, so I’ll try to be very clear here.

Add a UIView to your ViewController with a height of 100 and full width, and snug it up to the bottom of the view. Set the background to Clear Color. To help identify this view in your storyboard, you can open the Identity Inspector and give it an Xcode specific label of “ToolbarContainer”.

Next, add another UIView inside of your ToolbarContainer. Make sure in your scene that this view is a subview of ToolbarContainer.

Set the height to 70 and the Y-coordinate to 30, so it is also snug to the bottom. Give it a non white background so you can see it. Similar to the last view, give this one a label of “ButtonContainer”.

For both of the views you have added so far, go into the Size Inspector and modify the Autosizing to look like the following.

The reason we need both of these views will become apparent soon. The ToolbarContainer is going to hold everything, and it will be what we animate so that everything animates as one. The ButtonContainer view that we have set the background color on will hold all of our buttons.

We are going to add one more UIView called Touch Zone. This might seem like an excessive amount of views, but we need them all for what we are doing. This Touch Zone one will be the little tab that sticks up, and this is the only area we will allow the pan gesture to occur.

Place it inside the ToolbarContainer, but outside the ButtonContainer. Give it an X-coordinate of 256, Y- 0. Give it a width of 44, and height of 30. Set the background to the same color as your ButtonContainer, it should look like they are the same object. Make sure you set the Xcode specific label like we did with the other views so it makes it easier to pick out which view is which. Also set the autosizing as follows:

Let’s add the expand/collapse button now.

Place a UIButton inside the Touch Zone view. Set the x and y coordinates to 0. Set the width to 44 and height to 30. If you’ve seen my other tutorials you know I’m no graphics expert, so we’ll work with what we can to make it look decent.

Change the type to Custom, and set the background color to Clear Color. Now select the title label and delete the text “Button”, and go to Edit -> Special Characters (Command + Option + T). Find the downward arrow and double click it to set it as your button title.

Change your font to an Apple Gothic, GungSeo, or any other font that makes it a bold black arrow rather than a glossy blue button looking arrow. To see more fonts, select Custom in the Font menu and it opens up a lot more built in options than the system font. Set the font size to 21. I set the Content Inset - Top value to 6 and Left value to 3 to make the arrow look more in the center.

In the end, your view should look something like this.

Not amazing, but it will do. We could add the buttons to the ButtonContainer now if we wanted to. These would open up modal views, or whatever else you would want them to do. I think at this point though you’re probably anxious to get to the code, and really it is trivial to add those buttons.

Make the connections

Let’s start by getting all of our outlets and actions hooked up. Go to your ViewController.m file and replace the content with the following:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *toolbarContainer;
@property (nonatomic, weak) IBOutlet UIView *touchZone;
@property (nonatomic, weak) IBOutlet UIView *buttonContainer;
@property (nonatomic, weak) IBOutlet UIButton *collapseButton;
@end

@implementation ViewController

- (IBAction)expandCollapseButtonTouched
{
    // Expand or collapse the toolbar
}

@end

Now go back to the storyboard and hook up all these outlets with their corresponding views. We do need the button outlet because we will be changing the title label. Make sure to connect the arrow button’s touch up inside action to the expandCollapseButtonTouched action method.

Expand or collapse by button press

We will build up the functionality piece by piece, and the first piece is to implement a simple animation when the collapse button is touched.

Add a couple BOOL properties to the top of ViewController.m inside the @interface, which we will use to keep track of the toolbar state.

@property (nonatomic, assign) BOOL toolbarIsOpen;
@property (nonatomic, assign) BOOL toolbarIsAnimating;

Now replace the content in the @implementation with the following:

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.toolbarIsOpen = YES;
    self.toolbarIsAnimating = NO;
}

- (IBAction)expandCollapseButtonTouched
{
    if (!self.toolbarIsAnimating) {
        self.toolbarIsAnimating = YES;
        if (self.toolbarIsOpen) {
            [self collapseToolbarWithoutBounce];
        } else {
            [self expandToolbarWithoutBounce];
        }
    }
}

- (void)collapseToolbarWithoutBounce
{
    [UIView animateWithDuration:0.25 animations:^{
        [self.toolbarContainer setFrame:CGRectMake(0, (self.view.frame.size.height - 35), self.view.frame.size.width, 100.0)];
    } completion:^(BOOL finished) {
        self.toolbarIsOpen = NO;
        self.toolbarIsAnimating = NO;
        [self.collapseButton setTitle:@"\u2B06" forState:UIControlStateNormal];
    }];
}

- (void)expandToolbarWithoutBounce
{
    [UIView animateWithDuration:0.25 animations:^{
        [self.toolbarContainer setFrame:CGRectMake(0, (self.view.frame.size.height - 90), self.view.frame.size.width, 100.0)];
    } completion:^(BOOL finished) {
        self.toolbarIsOpen = YES;
        self.toolbarIsAnimating = NO;
        [self.collapseButton setTitle:@"\u2B07" forState:UIControlStateNormal];
    }];
}

@end

Breaking it down

In our viewDidLoad method we just initialize our toolbar state properties.

In our expandCollapseButtonTouched interface action method we first check if the toolbar is already animating. This stops the user from spamming the button and screwing up the animation. Next we set it to animating because we are about to begin the animation. Then we check if it is open or closed and call the corresponding method.

In collapseToolbarWithoutBounce we use the convenient UIView animate with duration method. We change the frame to have the y-value 35 points above the bottom of the screen. That’s 30 for our expand/collapse button and the extra 5 so you can see a bit of the toolbar showing so the user has an idea of what is down there. In the completion block we say that toolbarIsOpen is NO, and it is no longer animating. Then we change the title to an up arrow by using the unicode character.

In expandToolbarWithoutBounce we do everything from collapse, just opposite. When we set the new y-value we use the size of the whole view minus 90 so it is at the bottom for all screen sizes. We use 90 and not the full height of the container because in a minute we are going to add a bounce to the animation and we need those extra 10 points.

Build and run here. At this point you should have a functioning expanding and collapsing toolbar. You could stop here if this is all you wanted, but we are going to spend some more time making it better and fancier.

Let’s bounce

If you have used the Groupon app that features this type of toolbar you may have noticed that the animation has a little bounce to it. When expanding or collapsing it goes up a little higher before coming back down to it’s final resting point.

This is pretty easy to accomplish. First, go to the storyboard and move the ToolbarContainer y-value 10 points down so that part of the bar is not visible off the bottom of the screen.

Back in ViewController.m we are going to add collapseToolbar and expandToolbar methods. Basically, we will set off an animation to make the whole toolbar visible, then in the completion block we will run our original animation. Create these two methods:

- (void)collapseToolbar
{
    [UIView animateWithDuration:0.25 animations:^{
        [self.toolbarContainer setFrame:CGRectMake(0, (self.view.frame.size.height - 100), self.view.frame.size.width, 100.0)];
    } completion:^(BOOL finished) {
        [self collapseToolbarWithoutBounce];
    }];

}

- (void)expandToolbar
{
    [UIView animateWithDuration:0.25 animations:^{
        [self.toolbarContainer setFrame:CGRectMake(0, (self.view.frame.size.height - 100), self.view.frame.size.width, 100.0)];
    } completion:^(BOOL finished) {
        [self expandToolbarWithoutBounce];
    }];
}

Like I said, we call our original code in the completion block of our new animation. We also need to change expandCollapseButtonTouched to call these new animations instead of the old.

- (IBAction)expandCollapseButtonTouched
{
    if (!self.toolbarIsAnimating) {
        self.toolbarIsAnimating = YES;
        if (self.toolbarIsOpen) {
            [self collapseToolbar];
        } else {
            [self expandToolbar];
        }
    }
}

If you build and run now you should have a nice bounce to your animation.

Add the pan gesture

We are going to add a pan gesture so the user can grab the toolbar and drag it up or down to expand or collapse it rather than only having the button.

First thing we need to do is create the pan gesture and add it to the toolbarContainer view. Add the following code to the end of your viewDidLoad method.

UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveToolbar:)];
[self.touchZone addGestureRecognizer:panGesture];

Now we just have to implement the moveToolbar: method we set as the selector to call in the gesture. Add the following code and I will explain after.

- (void)moveToolbar:(UIPanGestureRecognizer *)panGesture
{
    CGPoint translatedPoint = [panGesture translationInView:self.toolbarContainer];

    if ([panGesture state] == UIGestureRecognizerStateBegan) {
        self.buttonContainer.backgroundColor = [UIColor colorWithRed:0.56 green:0.27 blue:0 alpha:1.0];
        self.touchZone.backgroundColor = [UIColor colorWithRed:0.56 green:0.27 blue:0 alpha:1.0];
    }

    if ([panGesture state] == UIGestureRecognizerStateChanged) {
        float newYOrigin = self.toolbarContainer.frame.origin.y + translatedPoint.y;
        if ((newYOrigin > (self.view.frame.size.height - 100)) && (newYOrigin < (self.view.frame.size.height - 35))) {
            self.toolbarContainer.center = CGPointMake(self.toolbarContainer.center.x, self.toolbarContainer.center.y + translatedPoint.y);
        }
        [panGesture setTranslation:CGPointMake(0, 0) inView:self.toolbarContainer];
    }

    if ([panGesture state] == UIGestureRecognizerStateEnded) {
        self.buttonContainer.backgroundColor = [UIColor colorWithRed:1.0 green:(128.0/255.0) blue:0 alpha:1.0];
        self.touchZone.backgroundColor = [UIColor colorWithRed:1.0 green:(128.0/255.0) blue:0 alpha:1.0];
        self.toolbarIsAnimating = YES;
        if (self.toolbarContainer.frame.origin.y < (self.view.frame.size.height - 65)) {
            [self expandToolbarWithoutBounce];
        } else {
            [self collapseToolbarWithoutBounce];
        }
    }
}

The translatedPoint gives us the x/y distance amount that the finger has moved during the pan.

When the state of the gesture is Began, we are going to change the background color of our toolbar like the Groupon app does.

The state is UIGestureRecognizerStateChanged if it recognizes a change to a continuous gesture. So in our case, this is true whenever the finger moves during the pan gesture. When the finger moves, we are going to change the y-location of our toolbarContainer, but first we have to make sure it isn’t too high or too low.

So we calculate the new y-origin, and if it is out of our bounds, we don’t update anything. If it is within our bounds, we add the translatedPoint offset to the center of the toolbarContainer. Basically, if I move my finger down 15 points, the center of our container moves down 15 points unless that is too far off the bottom of the screen.

The translation is cumulative, so if we don’t set it back to 0 our animation is going to go crazy.

Next, we check if the state is instead UIGestureRecognizerStateEnded. First we set the background color back to the original. Then we are going to check if it is above or below the halfway point and animate accordingly. This even handles the case where the toolbar is already open and the user drags it up 10 points.

Finishing touch

Since we are changing the background color on a pan gesture, we need to change the color when the user just touches the button. Add the following methods to the bottom of ViewController.m:

#pragma mark - Collapse Button Actions

- (IBAction)buttonTouchBegin
{
    self.buttonContainer.backgroundColor = [UIColor colorWithRed:0.56 green:0.27 blue:0 alpha:1.0];
    self.touchZone.backgroundColor = [UIColor colorWithRed:0.56 green:0.27 blue:0 alpha:1.0];
}

- (void)buttonTouchEnded
{
    self.buttonContainer.backgroundColor = [UIColor colorWithRed:1.0 green:(128.0/255.0) blue:0 alpha:1.0];
    self.touchZone.backgroundColor = [UIColor colorWithRed:1.0 green:(128.0/255.0) blue:0 alpha:1.0];
}

Modify the expandCollapseButtonTouched method to call buttonTouchEnded since it gets called on a “touch up inside” or in other words, when a touch ends.

- (IBAction)expandCollapseButtonTouched
{
    [self buttonTouchEnded];
    if (!self.toolbarIsAnimating) {
        self.toolbarIsAnimating = YES;
        if (self.toolbarIsOpen) {
            [self collapseToolbar];
        } else {
            [self expandToolbar];
        }
    }
}

Go back to the storyboard and connect the arrow button’s Touch Down event to the buttonTouchBegin action method.

Conclusion

That’s it. You can build and run here and everything should be fully functional. It should be easy enough to add buttons to the toolbar that pop up modal views. You do it the same way you would anywhere else, just make sure to put them in the ButtonContainer view. Since they are within the view we are animating they will automatically animate with it.

If you just want to see the whole ViewController.m file, you can see it in my Gists. Otherwise, you can get the completed project from my GitHub.