web-dev-qa-db-fra.com

Comment animer une barre de tabulation avec une transition de diapositive CrossDissolve?

J'essaie de créer un effet de transition sur une UITabBarController un peu similaire à l'application Facebook. J'ai réussi à obtenir un "effet de défilement" sur l'interrupteur à onglet, mais je n'arrive pas à comprendre comment résoudre le problème de la dissolution croisée (ou au moins, cela ne fonctionne pas).

Voici mon code actuel:

import UIKit

class ScrollingTabBarControllerDelegate: NSObject, UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        return ScrollingTransitionAnimator(tabBarController: tabBarController, lastIndex: tabBarController.selectedIndex)
    }
}

class ScrollingTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    weak var transitionContext: UIViewControllerContextTransitioning?
    var tabBarController: UITabBarController!
    var lastIndex = 0

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.2
    }

    init(tabBarController: UITabBarController, lastIndex: Int) {
        self.tabBarController = tabBarController
        self.lastIndex = lastIndex
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        self.transitionContext = transitionContext

        let containerView = transitionContext.containerView
        let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)

        containerView.addSubview(toViewController!.view)

        var viewWidth = toViewController!.view.bounds.width

        if tabBarController.selectedIndex < lastIndex {
            viewWidth = -viewWidth
        }

        toViewController!.view.transform = CGAffineTransform(translationX: viewWidth, y: 0)

        UIView.animate(withDuration: self.transitionDuration(using: (self.transitionContext)), delay: 0.0, usingSpringWithDamping: 1.2, initialSpringVelocity: 2.5, options: .transitionCrossDissolve, animations: {
            toViewController!.view.transform = CGAffineTransform.identity
            fromViewController!.view.transform = CGAffineTransform(translationX: -viewWidth, y: 0)
        }, completion: { _ in
            self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled)
            fromViewController!.view.transform = CGAffineTransform.identity
        })
    }
}

Ce serait génial si quelqu'un savait comment faire fonctionner cela, essayait depuis des jours sans progrès ...: /

edit: J'ai un cross dissolve en remplaçant le bloc UIView.animate par: 

UIView.transition(with: containerView, duration: 0.2, options: .transitionCrossDissolve, animations: {

    toViewController!.view.transform = CGAffineTransform.identity
    fromViewController!.view.transform = CGAffineTransform(translationX: -viewWidth, y: 0)

}, completion: { _ in

    self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled)
    fromViewController!.view.transform = CGAffineTransform.identity

})

Cependant, l'animation est vraiment lente et inutilisable :(

edit 2: Pour les personnes essayant d'utiliser ces extraits, n'oubliez pas de connecter le délégué pour la UITabBarController, sinon rien ne se passera.

edit 3: J'ai trouvé une bibliothèque Swift qui fait exactement ce que je cherchais: https://github.com/Interactive-Studio/TransitionableTab

9
JoniVR

Il existe un moyen plus simple de le faire. Ajoutez le code suivant dans le délégué de la barre d’onglet:

Travailler sur Swift 2, 3 et 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) Comme cette réponse est en train de devenir populaire, j'ai mis à jour le code pour supprimer la force déroulée, ce qui est une mauvaise pratique, et ajouté la déclaration de garde.

EDIT (7/11/18) @ AlbertoGarcía a raison. Si vous appuyez deux fois sur l'icône de la barre de tabulation, vous obtenez un écran vide. J'ai donc ajouté un chèque supplémentaire

41
gmogames

Si vous souhaitez utiliser UIViewControllerAnimatedTransitioning pour créer quelque chose de plus personnalisé que UIView.transition, consultez this Gist .

 enter image description here

// 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
    }
}
0
Derek Soike