How to animate Tab bar tab switch with a CrossDissolve slide transition? How to animate Tab bar tab switch with a CrossDissolve slide transition? ios ios

How to animate Tab bar tab switch with a CrossDissolve slide transition?


There is a simpler way to doing this. Add the following code in the tabbar delegate:

Working on Swift 2, 3 and 4

class MySubclassedTabBarController: UITabBarController {    override func viewDidLoad() {      super.viewDidLoad()      delegate = self    }}extension MySubclassedTabBarController: UITabBarControllerDelegate  {    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {        guard let fromView = selectedViewController?.view, let toView = viewController.view else {          return false // Make sure you want this as false        }        if fromView != toView {          UIView.transition(from: fromView, to: toView, duration: 0.3, options: [.transitionCrossDissolve], completion: nil)        }        return true    }}

EDIT (4/23/18)Since this answer is getting popular, I updated the code to remove the force unwraps, which is a bad practice, and added the guard statement.

EDIT (7/11/18)@AlbertoGarcía is right. If you tap the tabbar icon twice you get a blank screen. So I added an extra check


If you want to use UIViewControllerAnimatedTransitioning to do something more custom than UIView.transition, take a look at this gist.

enter image description here

// MyTabController.swiftimport UIKitclass MyTabBarController: UITabBarController {    override func viewDidLoad() {        super.viewDidLoad()        delegate = self    }}extension MyTabBarController: UITabBarControllerDelegate {    func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {        return MyTransition(viewControllers: tabBarController.viewControllers)    }}class MyTransition: NSObject, UIViewControllerAnimatedTransitioning {    let viewControllers: [UIViewController]?    let transitionDuration: Double = 1    init(viewControllers: [UIViewController]?) {        self.viewControllers = viewControllers    }    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {        return TimeInterval(transitionDuration)    }    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {        guard            let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),            let fromView = fromVC.view,            let fromIndex = getIndex(forViewController: fromVC),            let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),            let toView = toVC.view,            let toIndex = getIndex(forViewController: toVC)            else {                transitionContext.completeTransition(false)                return        }        let frame = transitionContext.initialFrame(for: fromVC)        var fromFrameEnd = frame        var toFrameStart = frame        fromFrameEnd.origin.x = toIndex > fromIndex ? frame.origin.x - frame.width : frame.origin.x + frame.width        toFrameStart.origin.x = toIndex > fromIndex ? frame.origin.x + frame.width : frame.origin.x - frame.width        toView.frame = toFrameStart        DispatchQueue.main.async {            transitionContext.containerView.addSubview(toView)            UIView.animate(withDuration: self.transitionDuration, animations: {                fromView.frame = fromFrameEnd                toView.frame = frame            }, completion: {success in                fromView.removeFromSuperview()                transitionContext.completeTransition(success)            })        }    }    func getIndex(forViewController vc: UIViewController) -> Int? {        guard let vcs = self.viewControllers else { return nil }        for (index, thisVC) in vcs.enumerated() {            if thisVC == vc { return index }        }        return nil    }}


I was struggling with the tab bar animation both from a user tap and programmatically calling selectedIndex = X since the accepted solution didn't work for me when setting the selected tab programatically.

In the end I managed to solve it by a UITabBarControllerDelegate and a custom UIViewControllerAnimatedTransitioning as follows:

extension MainController: UITabBarControllerDelegate {    public func tabBarController(            _ tabBarController: UITabBarController,            animationControllerForTransitionFrom fromVC: UIViewController,            to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {        return FadePushAnimator()    }}

Where the FadePushAnimator looks like this:

class FadePushAnimator: NSObject, UIViewControllerAnimatedTransitioning {    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {        return 0.3    }    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {        guard                let toViewController = transitionContext.viewController(forKey: .to)                else {            return        }        transitionContext.containerView.addSubview(toViewController.view)        toViewController.view.alpha = 0        let duration = self.transitionDuration(using: transitionContext)        UIView.animate(withDuration: duration, animations: {            toViewController.view.alpha = 1        }, completion: { _ in            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)        })    }}

This approach supports any sort of custom animation and works both on user tap and setting the selected tab programatically. Tested on Swift 5.