iPhone: How to switch tabs with an animation?
I'm switching tabs programmatically in a tab bar driven application using UITabBarController.selectedIndex
. The problem I'm trying to solve is how to animate the transition between the views. ie. from the view of the current tab to the view of the selected tab.
The first thought was to make use of the UITabBarControllerDelegate
, but it appears that this is not called when programmatically switching tabs. I'm now considering the UITabBarDelegate.didSelectItem
: as a possible hook to set a transition animation.
Has anyone managed to animate the transitions? If yes, how ?
Solution 1:
Update 04/2016: Justed wanted to update this to say thank you to everyone for all the votes. Please also note that this was originally written way back when ... before ARC, before constraints, before ... a lot of stuff! So please take this into account when deciding whether to use these techniques. There may be more modern approaches. Oh, and if you find one. Please add a response so everyone can see. Thanks.
Some time later ...
After much research I came up with two working solutions. Both of these worked and did the animation between tabs.
Solution 1: transition from view (simple)
This is the easiest and makes use of a predefined UIView transition method. With this solution we don't need to manage the views because the method does the work for us.
// Get views. controllerIndex is passed in as the controller we want to go to.
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];
// Transition using a page curl.
[UIView transitionFromView:fromView
toView:toView
duration:0.5
options:(controllerIndex > tabBarController.selectedIndex ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown)
completion:^(BOOL finished) {
if (finished) {
tabBarController.selectedIndex = controllerIndex;
}
}];
Solution 2: scroll (more complex)
A more complex solution, but gives you more control of the animation. In this example we get the views to slide on and off. With this one we need to manage the views ourselves.
// Get the views.
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];
// Get the size of the view area.
CGRect viewSize = fromView.frame;
BOOL scrollRight = controllerIndex > tabBarController.selectedIndex;
// Add the to view to the tab bar view.
[fromView.superview addSubview:toView];
// Position it off screen.
toView.frame = CGRectMake((scrollRight ? 320 : -320), viewSize.origin.y, 320, viewSize.size.height);
[UIView animateWithDuration:0.3
animations: ^{
// Animate the views on and off the screen. This will appear to slide.
fromView.frame =CGRectMake((scrollRight ? -320 : 320), viewSize.origin.y, 320, viewSize.size.height);
toView.frame =CGRectMake(0, viewSize.origin.y, 320, viewSize.size.height);
}
completion:^(BOOL finished) {
if (finished) {
// Remove the old view from the tabbar view.
[fromView removeFromSuperview];
tabBarController.selectedIndex = controllerIndex;
}
}];
This Solution in Swift:
extension TabViewController: UITabBarControllerDelegate {
public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
let fromView: UIView = tabBarController.selectedViewController!.view
let toView : UIView = viewController.view
if fromView == toView {
return false
}
UIView.transitionFromView(fromView, toView: toView, duration: 0.3, options: UIViewAnimationOptions.TransitionCrossDissolve) { (finished:Bool) in
}
return true
}
}
Solution 2:
following is my try to use the code form drekka into the delegate(UITabBarControllerDelegate) method
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSArray *tabViewControllers = tabBarController.viewControllers;
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = viewController.view;
if (fromView == toView)
return false;
NSUInteger fromIndex = [tabViewControllers indexOfObject:tabBarController.selectedViewController];
NSUInteger toIndex = [tabViewControllers indexOfObject:viewController];
[UIView transitionFromView:fromView
toView:toView
duration:0.3
options: toIndex > fromIndex ? UIViewAnimationOptionTransitionFlipFromLeft : UIViewAnimationOptionTransitionFlipFromRight
completion:^(BOOL finished) {
if (finished) {
tabBarController.selectedIndex = toIndex;
}
}];
return true;
}
Solution 3:
My solution for iOS7.0 or above.
You can specify a custom animation controller in the tab bar's delegate.
Implement an animation controller like this:
@interface TabSwitchAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation TabSwitchAnimationController
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.2;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController* toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView* toView = toVC.view;
UIView* fromView = fromVC.view;
UIView* containerView = [transitionContext containerView];
[containerView addSubview:toView];
toView.frame = [transitionContext finalFrameForViewController:toVC];
// Animate by fading
toView.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0.0
options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
toView.alpha = 1.0;
}
completion:^(BOOL finished) {
toView.alpha = 1.0;
[fromView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
@end
Then use it in your UITabBarControllerDelegate:
- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
animationControllerForTransitionFromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
return [[TabSwitchAnimationController alloc] init];
}
Solution 4:
Instead of use tabBarController:shouldSelectViewController:
is better to implement tabBarController:animationControllerForTransitionFromViewController:toViewController:
TransitioningObject.swift
import UIKit
class TransitioningObject: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView: UIView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView: UIView = transitionContext.viewForKey(UITransitionContextToViewKey)!
transitionContext.containerView().addSubview(fromView)
transitionContext.containerView().addSubview(toView)
UIView.transitionFromView(fromView, toView: toView, duration: transitionDuration(transitionContext), options: UIViewAnimationOptions.TransitionCrossDissolve) { finished in
transitionContext.completeTransition(true)
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.25
}
}
TabBarViewController.swift
import UIKit
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK: - Tabbar delegate
func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return TransitioningObject()
}
}
Solution 5:
I think you can easily achieve transitions for UITabBarControlelr using CATransition; This will also solve any side effects of using transitionFromView:toView:
Use this inside your custom TabBarController class extended from UITabBarController.
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController (UIViewController*)viewController {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionFade];
[animation setDuration:0.25];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseIn]];
[self.view.window.layer addAnimation:animation forKey:@"fadeTransition"];
}
Hope this helps :)