ios 如何使用 CrossDissolve 幻灯片过渡为 Tab bar 选项卡开关设置动画?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44346280/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 09:10:12  来源:igfitidea点击:

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

iosswiftuitabbarcontrolleruitabbar

提问by JoniVR

I'm trying to create a transition effect on a UITabBarControllersomewhat similar to the Facebook app. I managed to get a "scrolling effect" working on tab switch, but I can't seem to figure out how to cross dissolve (or it doesn't work at least).

我正在尝试在UITabBarController有点类似于 Facebook 应用程序上创建过渡效果。我设法在选项卡切换上获得了“滚动效果”,但我似乎无法弄清楚如何交叉溶解(或者它至少不起作用)。

Here's my current code:

这是我当前的代码:

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

Would be great if anyone know how to get this to work, been trying for days now without progress... :/

如果有人知道如何让它发挥作用,那就太好了,现在已经尝试了好几天没有进展......:/

edit:I got a cross dissolve working by replacing the UIView.animate block with:

编辑:通过将 UIView.animate 块替换为:

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

})

However, the animation is really laggy and not usable :(

然而,动画真的很慢而且不可用:(

edit 2:For people trying to use these snippets, don't forget to hook up the delegate for the UITabBarController, otherwise nothing will happen.

编辑 2:对于尝试使用这些片段的人,不要忘记为 连接委托UITabBarController,否则什么都不会发生。

edit 3:I've found a Swift library that does exactly what I was looking for: https://github.com/Interactive-Studio/TransitionableTab

编辑 3:我找到了一个 Swift 库,它完全符合我的要求:https: //github.com/Interactive-Studio/TransitionableTab

回答by gmogames

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

有一种更简单的方法可以做到这一点。在标签栏委托中添加以下代码:

Working on Swift 2, 3 and 4

使用 Swift 2、3 和 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.

编辑(2018 年 4 月 23 日)由于这个答案越来越流行,我更新了代码以删除强制解包,这是一种不好的做法,并添加了守卫语句。

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

编辑 (7/11/18)@AlbertoGarcía 是对的。如果您点击标签栏图标两次,您会看到一个空白屏幕。所以我添加了一个额外的检查

回答by Derek Soike

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

如果你想用来UIViewControllerAnimatedTransitioning做一些更自定义的事情UIView.transition,看看这个要点

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

回答by Tomask

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

我在用户点击和以编程方式调用时都在努力处理标签栏动画,selectedIndex = X因为在以编程方式设置所选标签时,接受的解决方案对我不起作用。

In the end I managed to solve it by a UITabBarControllerDelegateand a custom UIViewControllerAnimatedTransitioningas follows:

最后我设法通过一个UITabBarControllerDelegate和一个自定义来解决它,UIViewControllerAnimatedTransitioning如下所示:

extension MainController: UITabBarControllerDelegate {

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

Where the FadePushAnimator looks like this:

FadePushAnimator 看起来像这样:

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.

这种方法支持任何类型的自定义动画,并且适用于用户点击和以编程方式设置所选选项卡。在 Swift 5 上测试。

回答by TheJeff

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

要扩展@gmogames 答案: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.

我仍然认为不幸的是,要完成这项工作,我们必须覆盖一个不是 setter 的方法并在其中操作数据。如果这是我们需要重写以完成此操作的方法,它不会使选项卡栏控制器具有应有的可扩展性。