IPhone: 如何用动画切换标签?

我正在使用 UITabBarController.selectedIndex在一个标签栏驱动的应用程序中以编程方式切换标签。我试图解决的问题是如何动画化视图之间的转换。也就是说。从当前选项卡的视图到选定选项卡的视图。

第一个想法是利用 UITabBarControllerDelegate,但似乎这是没有调用时,编程切换选项卡。我现在考虑的 UITabBarDelegate.didSelectItem: 作为一个可能的钩子设置过渡动画。

有人设法把过渡动画化了吗? 如果有,怎么做?

57879 次浏览

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
}
}

a fix for the jumpy animation...

UIView * fromView = self.view.superview;

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

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;
}

this can be solved in two ways

1 - Write this in your AppDelegate.m file once. Remember to include UITabBarControllerDelegate using <> after colon (:) in your AppDelegate.h

-(void)tabBarController:(UITabBarController *)tabBarControllerThis didSelectViewController:(UIViewController *)viewController
{
[UIView transitionWithView:viewController.view
duration:0.1
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionTransitionCrossDissolve
animations:^(void){
} completion:^(BOOL finished){
[UIView beginAnimations:@"animation" context:nil];
[UIView setAnimationDuration:0.7];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:viewController.view
cache:NO];
[UIView commitAnimations];
}];
}

2 - Write this in each of your ViewController.m file

-(void)viewWillAppear:(BOOL)animated
{
[UIView transitionWithView:self.view
duration:1.0
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionTransitionCrossDissolve
animations:^(void){
[super viewWillAppear:YES];
} completion:^(BOOL finished){
}];
}

hope this help...!

drekka's answer is really great. I tweaked the scroll transition a bit so the animation looked more like Apple's push animation. I added an additional animation upon the completion of the first animation to get that sliding effect to look right.

// Disable interaction during animation to avoids bugs.
self.tabBarController.view.userInteractionEnabled = NO;


// 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];
[fromView.superview addSubview:fromView];


self.tabBarController.selectedIndex = 0;


// Position it off screen.
toView.frame = CGRectMake((scrollRight ? (viewSize.size.width *.25) : -(viewSize.size.width * .25 )), viewSize.origin.y, viewSize.size.width, viewSize.size.height);


[UIView animateWithDuration:0.25
animations: ^{
// Animate the views on and off the screen.
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
fromView.frame = CGRectMake(viewSize.size.width * .95, viewSize.origin.y, viewSize.size.width, viewSize.size.height);
toView.frame = CGRectMake((viewSize.origin.x * .90), viewSize.origin.y, viewSize.size.width, viewSize.size.height);
}


completion:^(BOOL finished) {
if (finished) {
// Being new animation.
[UIView animateWithDuration:0.2
animations: ^{
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
fromView.frame = CGRectMake(viewSize.size.width, viewSize.origin.y, viewSize.size.width, viewSize.size.height);
toView.frame = CGRectMake((viewSize.origin.x), viewSize.origin.y, viewSize.size.width, viewSize.size.height);
}
completion:^(BOOL finished) {
if (finished) {
// Remove the old view from the tabbar view.
[fromView removeFromSuperview];
// Restore interaction.
self.tabBarController.view.userInteractionEnabled = YES;
}
}];
}
}];

My solution in Swift:

Create custom TabBar class and set it in your storyboard TabBar

class MainTabBarController: UITabBarController, UITabBarControllerDelegate {


override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
// Do any additional setup after loading the view.
}


func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {




let tabViewControllers = tabBarController.viewControllers!
let fromView = tabBarController.selectedViewController!.view
let toView = viewController.view


if (fromView == toView) {
return false
}


let fromIndex = tabViewControllers.indexOf(tabBarController.selectedViewController!)
let toIndex = tabViewControllers.indexOf(viewController)


let offScreenRight = CGAffineTransformMakeTranslation(toView.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-toView.frame.width, 0)


// start the toView to the right of the screen




if (toIndex < fromIndex) {
toView.transform = offScreenLeft
fromView.transform = offScreenRight
} else {
toView.transform = offScreenRight
fromView.transform = offScreenLeft
}


fromView.tag = 124
toView.addSubview(fromView)


self.view.userInteractionEnabled = false
UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {


toView.transform = CGAffineTransformIdentity


}, completion: { finished in


let subViews = toView.subviews
for subview in subViews{
if (subview.tag == 124) {
subview.removeFromSuperview()
}
}
tabBarController.selectedIndex = toIndex!
self.view.userInteractionEnabled = true


})


return true
}


}

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];
}

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()
}
}

I used @Mofumofu's solution and upgraded it to Swift 1.2 and also implemented an up / down animation. Meaning, the new ViewController comes up and pushes the old one up if the new viewcontroller's index is greater than the old one's. Otherwise the direction is down.

class TabScrollPageAnimationController: NSObject, UIViewControllerAnimatedTransitioning {


let tabBarController: UITabBarController


init(tabBarController: UITabBarController) {
self.tabBarController = tabBarController
}


func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}


func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
let fromView = fromVC.view
let toView = toVC.view


let containerView = transitionContext.containerView()


var directionUpwardMultiplier: CGFloat = 1.0
if let vcs = tabBarController.viewControllers as? [UIViewController],
let fIndex = find(vcs, fromVC),
let tIndex = find(vcs, toVC) {
directionUpwardMultiplier = (fIndex < tIndex) ? +1.0 : -1.0
}


containerView.clipsToBounds = false
containerView.addSubview(toView)


var fromViewEndFrame = fromView.frame
fromViewEndFrame.origin.y -= (containerView.frame.height * directionUpwardMultiplier)


let toViewEndFrame = transitionContext.finalFrameForViewController(toVC)
var toViewStartFrame = toViewEndFrame
toViewStartFrame.origin.y += (containerView.frame.height * directionUpwardMultiplier)
toView.frame = toViewStartFrame


toView.alpha = 0.0
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
toView.alpha = 1.0
toView.frame = toViewEndFrame
fromView.alpha = 0.0
fromView.frame = fromViewEndFrame
}, completion: { (completed) -> Void in
toView.alpha = 1.0
fromView.removeFromSuperview()
transitionContext.completeTransition(completed)
containerView.clipsToBounds = true
})


}
}


}

In the Container ViewController:

extension XYViewController: UITabBarControllerDelegate {


func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return TabScrollPageAnimationController(tabBarController: tabBarController)
}


}

I wanted to use a flip transition between two child view controllers on a button press and achieved it as follows:

-(IBAction)flipViewControllers:(id)sender{
NSUInteger index = self.selectedIndex;
index++;
if(index >= self.childViewControllers.count){
index = 0;
}


self.selectedIndex = index;


[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:index % 2 ? UIViewAnimationTransitionFlipFromLeft : UIViewAnimationTransitionFlipFromRight
forView:self.view
cache:YES];
[UIView commitAnimations];
}

I also set the background colour to black, in my case I did that by setting the navigationController.view.backgroundColor but in your case it might be the window.backgroundColor which can easily be set in the app delegate.

Here's my working code (for 3 tabs, haven't tried it on more!!) to animate transitions between tabs. It's mainly based on drekka's solution, but already implemented in the tabbar's delegate method, so it should do the job if you just copy/paste it.. (you never know!)

-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {


// Important! We validate that the selected tab is not the current tab, to avoid misplacing views
if (tabBarController.selectedViewController == viewController) {
return NO;
}


// Find the selected view's index
NSUInteger controllerIndex = 0;
for (UIViewController *vc in tabBarController.viewControllers) {
if (vc == viewController) {
controllerIndex = [tabBarController.viewControllers indexOfObject:vc];
}
}


CGFloat screenWidth = SCREEN_SIZE.width;


// Note: We must invert the views according to the direction of the scrolling ( FROM Left TO right or FROM right TO left )
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = viewController.view;


[fromView.superview addSubview:toView];
CGRect fromViewInitialFrame = fromView.frame;
CGRect fromViewNewframe = fromView.frame;


CGRect toViewInitialFrame = toView.frame;


if ( controllerIndex > tabBarController.selectedIndex ) {
// FROM left TO right ( tab0 to tab1 or tab2 )


// The final frame for the current view. It will be displaced to the left
fromViewNewframe.origin.x = -screenWidth;
// The initial frame for the new view. It will be displaced to the left
toViewInitialFrame.origin.x = screenWidth;
toView.frame = toViewInitialFrame;


} else {
// FROM right TO left ( tab2 to tab1 or tab0 )


// The final frame for the current view. It will be displaced to the right
fromViewNewframe.origin.x = screenWidth;
// The initial frame for the new view. It will be displaced to the right
toViewInitialFrame.origin.x = -screenWidth;
toView.frame = toViewInitialFrame;
}


[UIView animateWithDuration:0.2 animations:^{
// The new view will be placed where the initial view was placed
toView.frame = fromViewInitialFrame;
// The initial view will be place outside the screen bounds
fromView.frame = fromViewNewframe;


tabBarController.selectedIndex = controllerIndex;


// To prevent user interaction during the animation
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];


} completion:^(BOOL finished) {


// Before removing the initial view, we adjust its frame to avoid visual lags
fromView.frame = CGRectMake(0, 0, fromView.frame.size.width, fromView.frame.size.height);
[fromView removeFromSuperview];


[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];


return NO;

}

I wrote a post after trying the various answers here.

The code is in Swift, and you can programatically change the tab with animation by calling animateToTab.

func animateToTab(toIndex: Int) {
let tabViewControllers = viewControllers!
let fromView = selectedViewController!.view
let toView = tabViewControllers[toIndex].view
let fromIndex = tabViewControllers.indexOf(selectedViewController!)


guard fromIndex != toIndex else {return}


// Add the toView to the tab bar view
fromView.superview!.addSubview(toView)


// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.mainScreen().bounds.size.width;
let scrollRight = toIndex > fromIndex;
let offset = (scrollRight ? screenWidth : -screenWidth)
toView.center = CGPoint(x: fromView.center.x + offset, y: toView.center.y)


// Disable interaction during animation
view.userInteractionEnabled = false


UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {


// Slide the views by -offset
fromView.center = CGPoint(x: fromView.center.x - offset, y: fromView.center.y);
toView.center   = CGPoint(x: toView.center.x - offset, y: toView.center.y);


}, completion: { finished in


// Remove the old view from the tabbar view.
fromView.removeFromSuperview()
self.selectedIndex = toIndex
self.view.userInteractionEnabled = true
})
}

If you want all tab change to have the animation, then hook it to UITabBarControllerDelegate like as such:

func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
let tabViewControllers = tabBarController.viewControllers!
guard let toIndex = tabViewControllers.indexOf(viewController) else {
return false
}


// Our method
animateToTab(toIndex)


return true
}

This works for me in Swift 3:

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {


if let fromView = tabBarController.selectedViewController?.view, let toView = viewController.view {


if fromView == toView {
return false
}


UIView.transition(from: fromView, to: toView, duration: 0.2, options: .transitionCrossDissolve) { (finished) in
}
}


return true
}

Here is my Swift 3 solution:

I override selectedIndex of my UITabBarViewController like this:

override var selectedIndex: Int{
get{
return super.selectedIndex
}
set{
animateToTab(toIndex: newValue)
super.selectedIndex = newValue
}
}

Then I use this function that mimics native push/pop animation:

func animateToTab(toIndex: Int) {
guard let tabViewControllers = viewControllers, tabViewControllers.count > toIndex, let fromViewController = selectedViewController, let fromIndex = tabViewControllers.index(of: fromViewController), fromIndex != toIndex else {return}


view.isUserInteractionEnabled = false


let toViewController = tabViewControllers[toIndex]
let push = toIndex > fromIndex
let bounds = UIScreen.main.bounds


let offScreenCenter = CGPoint(x: fromViewController.view.center.x + bounds.width, y: toViewController.view.center.y)
let partiallyOffCenter = CGPoint(x: fromViewController.view.center.x - bounds.width*0.25, y: fromViewController.view.center.y)


if push{
fromViewController.view.superview?.addSubview(toViewController.view)
toViewController.view.center = offScreenCenter
}else{
fromViewController.view.superview?.insertSubview(toViewController.view, belowSubview: fromViewController.view)
toViewController.view.center = partiallyOffCenter
}


UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseIn, animations: {
toViewController.view.center   = fromViewController.view.center
fromViewController.view.center = push ? partiallyOffCenter : offScreenCenter
}, completion: { finished in
fromViewController.view.removeFromSuperview()
self.view.isUserInteractionEnabled = true
})
}

I hope it helps :)

@samwize Answer translated to Swift 3- 2 thumbs up on this one, creates a left to wright page effect:

func animateToTab(toIndex: Int) {
let tabViewControllers = viewControllers!
let fromView = selectedViewController!.view
let toView = tabViewControllers[toIndex].view
let fromIndex = tabViewControllers.index(of: selectedViewController!)


guard fromIndex != toIndex else {return}


// Add the toView to the tab bar view
fromView?.superview!.addSubview(toView!)


// Position toView off screen (to the left/right of fromView)
let screenWidth = screenSize.width
let scrollRight = toIndex > fromIndex!
let offset = (scrollRight ? screenWidth : -screenWidth)
toView?.center = CGPoint(x: (fromView?.center.x)! + offset, y: (toView?.center.y)!)


// Disable interaction during animation
view.isUserInteractionEnabled = false


UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {


// Slide the views by -offset
fromView?.center = CGPoint(x: (fromView?.center.x)! - offset, y: (fromView?.center.y)!);
toView?.center   = CGPoint(x: (toView?.center.x)! - offset, y: (toView?.center.y)!);


}, completion: { finished in


// Remove the old view from the tabbar view.
fromView?.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}

You can animate depending on the tapped Item - In this example we flipFromLeft if the tapped index is > than the previous selected index and we flipFromRight if the tapped index is < than the previous selected index. This is Swift 4: Implement the UITabBarControllerDelegate method

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {


let fromView: UIView = tabBarController.selectedViewController!.view
let toView: UIView = viewController.view


if fromView == toView {
return false
}


if let tappedIndex = tabBarController.viewControllers?.index(of: viewController) {
if tappedIndex > tabBarController.selectedIndex {
UIView.transition(from: fromView, to: toView, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromLeft, completion: nil)
} else {
UIView.transition(from: fromView, to: toView, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromRight, completion: nil)
}
}
return true
}

Swift 4+

You UITabBarControllerDelegate method should be like this,

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {


animateToTab(toIndex: (tabBarController.viewControllers?.index(of: viewController))!)
return true
}

And the method is,

func animateToTab(toIndex: Int) {
let tabViewControllers = viewControllers!
let fromView = selectedViewController!.view
let toView = tabViewControllers[toIndex].view
let fromIndex = tabViewControllers.index(of: selectedViewController!)


guard fromIndex != toIndex else {return}


// Add the toView to the tab bar view
fromView!.superview!.addSubview(toView!)


// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.main.bounds.size.width;
let scrollRight = toIndex > fromIndex!;
let offset = (scrollRight ? screenWidth : -screenWidth)
toView!.center = CGPoint(x: fromView!.center.x + offset, y: toView!.center.y)


// Disable interaction during animation
view.isUserInteractionEnabled = false


UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {


// Slide the views by -offset
fromView!.center = CGPoint(x: fromView!.center.x - offset, y: fromView!.center.y);
toView!.center   = CGPoint(x: toView!.center.x - offset, y: toView!.center.y);


}, completion: { finished in


// Remove the old view from the tabbar view.
fromView!.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
});


}

@samwize's answer updated for Swift 5:

If you want all tab changes to have the animation, then use a UITabBarControllerDelegate and implement this method:

func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let tabViewControllers = tabBarController.viewControllers!
guard let toIndex = tabViewControllers.indexOf(value:viewController) else {
return false
}
animateToTab(toIndex: toIndex, fadeOutFromView: false, fadeInToView: false)
return true
}

Programmatically change the tab with animation by calling animateToTab:

func animateToTab(toIndex: Int, fadeOutFromView: Bool, fadeInToView: Bool) {
let tabViewControllers = viewControllers!
let fromView = selectedViewController!.view
let toView = tabViewControllers[toIndex].view
let fromIndex = tabViewControllers.indexOf(value:selectedViewController!)
guard fromIndex != toIndex else {return}


// Add the toView to the tab bar view
fromView!.superview!.addSubview(toView!)


// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.main.bounds.width
let scrollRight = toIndex > fromIndex!;
let offset = (scrollRight ? screenWidth : -screenWidth)
toView!.center = CGPoint(x: fromView!.center.x + offset, y: toView!.center.y)


// Disable interaction during animation
view.isUserInteractionEnabled = false
if fadeInToView {
toView!.alpha = 0.1
}


UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseOut], animations: {


if fadeOutFromView {
fromView!.alpha = 0.0
}


if fadeInToView {
toView!.alpha = 1.0
}


// Slide the views by -offset
fromView!.center = CGPoint(x: fromView!.center.x - offset, y: fromView!.center.y);
toView!.center   = CGPoint(x: toView!.center.x - offset, y: toView!.center.y);


}, completion: { finished in
// Remove the old view from the tabbar view.
fromView!.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}