Full Source code: https://github.com/boctor/idev-recipes/tree/master/TabBarAnimation
Problem:
The Twitter iPhone App has a small arrow indicator above the tab bar that animates when a tab is selected. We want to recreate this animation.
Solution:
The arrow is simply another image added on top of the tab bar which is animated every time the tab selection changes. So we have two tasks:
- Add the arrow on top of the tab bar. This is similar to what we did in our last recipe.
- Animate the arrow when a new tab is selected.
To add arrow on top of the tab, we have to figure out the proper horizontal and vertical locations.
Vertical Location
The vertical location is always the same so we’ll figure it out just once. To calculate the vertical location, we start at the bottom of the window, go up by height of the tab bar, go up again by the height of arrow and then come back down 2 pixels so the arrow is slightly on top of the tab bar:
CGFloat verticalLocation = self.window.frame.size.height - tabBarController.tabBar.frame.size.height - tabBarArrowImage.size.height + 2;
Horizontal Location
The horizontal location will change depending on which tab bar is currently selected. So we’ll write a method that given a tab index figures out the horizontal location.
There is nothing too complicated here: We divide the width of the tab bar by the number of items to calculate the width of a single item. We then multiply the index by the width of single item and add half the width of an item so the arrow lands in the middle:
- (CGFloat) horizontalLocationFor:(NSUInteger)tabIndex { // A single tab item's width is the entire width of the tab bar divided by number of items CGFloat tabItemWidth = tabBarController.tabBar.frame.size.width / tabBarController.tabBar.items.count; // A half width is tabItemWidth divided by 2 minus half the width of the arrow CGFloat halfTabItemWidth = (tabItemWidth / 2.0) - (tabBarArrow.frame.size.width / 2.0); // The horizontal location is the index times the width plus a half width return (tabIndex * tabItemWidth) + halfTabItemWidth; }
Add the arrow on top of the tab bar
On app startup we add the arrow on top of the selected tab. Our sample app doesn’t remember which tab you had selected before you quit, so we always start at index 0 ([self horizontalLocationFor:0]):
- (void) addTabBarArrow { UIImage* tabBarArrowImage = [UIImage imageNamed:@"TabBarNipple.png"]; self.tabBarArrow = [[[UIImageView alloc] initWithImage:tabBarArrowImage] autorelease]; // To get the vertical location we start at the bottom of the window, go up by height of the tab bar, go up again by the height of arrow and then come back down 2 pixels so the arrow is slightly on top of the tab bar. CGFloat verticalLocation = self.window.frame.size.height - tabBarController.tabBar.frame.size.height - tabBarArrowImage.size.height + 2; tabBarArrow.frame = CGRectMake([self horizontalLocationFor:0], verticalLocation, tabBarArrowImage.size.width, tabBarArrowImage.size.height); [self.window addSubview:tabBarArrow]; }
Animate the arrow when a new tab is selected
A UITabBarController delegate gets notified every time a view controller was selected. We’ll use this as the trigger for starting the animation.
The actual animation is very simple. We use animation blocks available on every view.
If you haven’t used animation blocks before, here is a simple description:
- Before you start the animation block set the frame of the item you want to animate to the start location.
- Inside the animation block set the frame of the item you want to animate to the end location.
That’s all you have to do. The OS figures out the intermediate frames and does the actual animation for you. Doesn’t get simpler than that.
The arrow is already at the location we want it to animate from, so we don’t have to do anything before we start the animation block.
Inside the animation block block, all we have to do is set the final location of the arrow. So we take the existing frame of the arrow and change its horizontal location based on the newly selected tab index:
- (void)tabBarController:(UITabBarController *)theTabBarController didSelectViewController:(UIViewController *)viewController { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.2]; CGRect frame = tabBarArrow.frame; frame.origin.x = [self horizontalLocationFor:tabBarController.selectedIndex]; tabBarArrow.frame = frame; [UIView commitAnimations]; }
There a couple of things you can play around with to customize the animation:
- The value you pass to setAnimationDuration will speed up or slow down the animation.
- You can also set an animation curve. The default curve is UIViewAnimationCurveEaseInOut which causes the animation to start slowly, get faster in the middle and then slow before the animation is complete. Other curves like UIViewAnimationCurveEaseIn cause the animation to start slowly and then get faster until completion.
Full Source code: https://github.com/boctor/idev-recipes/tree/master/TabBarAnimation
I wish this were that easy in Android.
Thanks for the post, useful.
I found it better to add the imageview as a subview of the tab bar and not as a subview of the window as there are parts of my app where the tab bar is hidden.
[self.tabBarController.tabBar addSubview:tabBarArrow];
You also need to update the verticalPosition calculation to take this into account. A value of -3 or -4 looks about right.
Good suggestion. To be safe, I’d also recommend explicitly setting clipsToBounds to NO on the tabBar, since tabBarArrow is now beyond the bounds of the tabBar.
hi, I am interesting with the exmplae, I have one question, how could I add one startup view which without the tabbar appears? here is the app that I am doing:startup menu : when the app launched, user will see this as a cover page , then it disappeared after few seconds, then user see the tabbar views.
If you want to use it with Iphone 5 and 4 togheter, i suggest you:
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) &&
([UIScreen mainScreen].bounds.size.height > 480.0f)) {
CGFloat verticalLocation = self.window.frame.size.height – tabBarController.tabBar.frame.size.height – tabBarArrowImage.size.height + 2;
tabBarArrow.frame = CGRectMake([self horizontalLocationFor:0], verticalLocation, tabBarArrowImage.size.width, tabBarArrowImage.size.height);
} else {
CGFloat verticalLocation = self.window.frame.size.height – tabBarController.tabBar.frame.size.height – tabBarArrowImage.size.height – 87;
tabBarArrow.frame = CGRectMake([self horizontalLocationFor:0], verticalLocation, tabBarArrowImage.size.width, tabBarArrowImage.size.height);
}
Thank you for this great post!
I changed something on the code to add subview directly at the UITabBarController (so i refer to self.view and not to self.window) and it works fine. While define pragmatically verticalLocation, with a Navigation Bar in that view, cause me a little headache 😛
it would be nice have a “recipe” about custom UITabBarItem(s) used in twitter app, i’m curious to understand how they used image like TabBarItemSelectedBackground.png and TabBarSelection.png to build tabBarItems.
😀
Hi Yari, our latest recipe shows how to recreate Twitter’s tab bar
Thank you Peter,
That’s exactly what i was looking for!
Thank you very much for the post!!
With a small modification on your code I made it work on landscape. It can be a little trick though, here it follows: https://gist.github.com/762676
I hope this can help =)
Hey ike,
thanks a lot for the landscape code tweaks – I tried it myself but I ended up in buggy code 😉
Cheers,
Felix
Hello,
First, thank you for your tutorial.
Your result is quite good but one special feature of the twitter TabBar is this vertical size : 44px. This is what make possible the nice transition towards the toolbar when you click on a tweet.
Don’t you think they have made it from scratch ?
Even the gradient in the TabBar when a button isn’t clicked is stronger than the original apple TabBar. I tried to make the TabBar from scratch ( http://cashtag.files.wordpress.com/2010/12/depenses1.png ) and for the moment this is the nearest I can get. It’s a custom view with images but with a little work of quartz gradient, I’m sure you can obtain almost the same TabBarItem as the original one.
Thank you for your time and your tutorials, it’s very interesting 😉
Check our latest blog post where we recreate Twitter’s custom tab bar: http://idevrecipes.com/2011/01/04/how-does-the-twitter-iphone-app-implement-a-custom-tab-bar/
Sorry for my previous post, I was a little off-board because your article is about the tab bar indicator and not the tab bar itself 😉
Thanks anyway 😉
Is there any problem if we add the UIImageView to the tabBarController.tabBar instead of the window.view ? I was trying to support upsidedown orientation to but if I added to the window the UIImageView don’t rotate. I fix it adding it to the tabBar.
I was able to make it work in portrait mode…what about in landscape? Any ideas?
Thanks in advance,
Davide
Thank you ike!
It don’t work well with modal view. when I presentModalViewController, the nipple image doesn’t disappear. It is still display above the modal view.
how to use this with StoryBords.