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

我的未来我决定 提交于 2019-11-28 17:19:46

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.

// MyTabController.swift

import UIKit

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

To expand on @gmogames answer: https://stackoverflow.com/a/45362914/1993937

I couldn't get this to animate when selecting the tab bar index via code, as calling:

tabBarController.setSeletedIndex(0)

Doesn't seem to go through the same call heirarchy, and it skips the method:

tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController)

entirely, resulting in no animation.

In my code I wanted to have an animation transition for a user tapping a tab bar item in addition to me setting the tab bar item in-code manually under certain circumstances.

Here is my addition to the solution above which adds a different method to set the selected index via code that will animate the transition:

import Foundation
import UIKit

@objc class CustomTabBarController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }

    @objc func set(selectedIndex index : Int) {
        _ = self.tabBarController(self, shouldSelect: self.viewControllers![index])
    }
}

@objc extension CustomTabBarController: UITabBarControllerDelegate  {
    @objc 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: { (true) in

            })

            self.selectedViewController = viewController
        }

        return true
    }
}

Now just call

tabBarController.setSelectedWithIndex(1)   

for an in-code animated transition!

I still think it is unfortunate that to get this done we have to override a method that isn't a setter and manipulate data within it. It doesn't make the tab bar controller as extensible as it should be if this is the method that we need to override to get this done.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!