我如何禁用UIStackView的默认显示/隐藏动画?

3
这可能是一个奇怪的问题。当我们在UIStackView中显示或隐藏视图时,我们可以免费获得动画效果。但这种行为与我拥有的另一个动画冲突。那么有没有一种方法可以禁用UIStackView的默认动画?
我希望它只是显示或隐藏子视图而没有任何动画。我如何使用swift实现这一点?
更新
所以,如果我执行view2.isHidden = true,StackView将默认使用折叠动画隐藏View2。我希望它只是强制隐藏而没有动画。

enter image description here


1
你能提供一些代码吗?如果我没记错的话,UIStackView没有任何默认动画。 - Stanislav Marynych
可以的。您可以进行简单的测试,当您隐藏UIStackView中的视图时,可以看到收缩动画。我将更新问题,尝试使其更加直观。 - rickrvo
1
@rickrvo - 不管你是否认同,你必须向我们展示一些代码。看看这个例子:https://pastebin.com/mtk1jhkP ... 它将两个视图添加到垂直堆栈视图中,将该堆栈视图添加到另一个视图中,然后在任何地方轻触都会切换第二个排列子视图的.isHidden属性。没有动画--即使使用Debug -> Slow Animations也是如此。 - DonMag
1个回答

3

没有更多的信息,我猜测您正在进行类似以下的操作:

    self.view2.isHidden.toggle()

    // animate constraint change
    self.animLeadingConstraint.isActive.toggle()
    self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
    UIView.animate(withDuration: 0.5, animations: {
        self.view.layoutIfNeeded()
    })

你会得到“堆栈视图动画”的原因是:
    // nothing happening between
    //  hide / show arranged subview
    // and
    //  the animation block

    // so, this is the START of the "animation"
    self.view2.isHidden.toggle()

    // animate constraint change
    self.animLeadingConstraint.isActive.toggle()
    self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
    UIView.animate(withDuration: 0.5, animations: {
        self.view.layoutIfNeeded()
    })

有各种避免这种情况的方法,包括:

    // animate constraint change
    self.animLeadingConstraint.isActive.toggle()
    self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
    UIView.animate(withDuration: 0.5, animations: {
        self.view.layoutIfNeeded()
    })
    
    // hide / show arranged subview AFTER animation block
    self.view2.isHidden.toggle()
    

并且:

    // hide / show arranged subview
    self.view2.isHidden.toggle()
    
    // force layout update
    self.view.setNeedsLayout()
    self.view.layoutIfNeeded()
    
    // now start the animation

    // animate constraint change
    self.animLeadingConstraint.isActive.toggle()
    self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
    UIView.animate(withDuration: 0.5, animations: {
        self.view.layoutIfNeeded()
    })

这是一个完整的示例,说明它们之间的区别:

class ViewController: UIViewController {
    
    let stackView: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        return v
    }()

    let animView = UILabel()
    let view1 = UILabel()
    let view2 = UILabel()
    let stackContainer = UIView()
    
    var animLeadingConstraint: NSLayoutConstraint!
    var animTrailingConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        // add three buttons at the top
        let btnsStack = UIStackView()
        btnsStack.spacing = 20
        btnsStack.distribution = .fillEqually
        
        ["Default", "Fix 1", "Fix 2"].forEach { str in
            let b = UIButton()
            b.setTitle(str, for: [])
            b.setTitleColor(.white, for: .normal)
            b.setTitleColor(.gray, for: .highlighted)
            b.backgroundColor = .systemGreen
            b.addTarget(self, action: #selector(btnTap(_:)), for: .touchUpInside)
            btnsStack.addArrangedSubview(b)
        }
        
        for (v, s) in zip([animView, view1, view2], ["Will Animate", "View 1", "View 2"]) {
            v.text = s
            v.textAlignment = .center
            v.layer.borderWidth = 2
            v.layer.borderColor = UIColor.red.cgColor
        }
        
        animView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)

        stackContainer.backgroundColor = .systemTeal
        
        [btnsStack, stackView, stackContainer, animView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }

        stackView.addArrangedSubview(view1)
        stackView.addArrangedSubview(view2)
        
        stackContainer.addSubview(stackView)
        
        view.addSubview(btnsStack)
        view.addSubview(stackContainer)
        view.addSubview(animView)
        
        let g = view.safeAreaLayoutGuide
        
        // Leading and Trailing constraints for the animView
        //  so we can "slide" it back and forth
        animLeadingConstraint = animView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0)
        animLeadingConstraint.priority = .defaultHigh
        animTrailingConstraint = animView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0)
        animTrailingConstraint.priority = .defaultHigh

        NSLayoutConstraint.activate([
            
            btnsStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),

            stackContainer.topAnchor.constraint(equalTo: btnsStack.bottomAnchor, constant: 40.0),
            stackContainer.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            stackView.topAnchor.constraint(equalTo: stackContainer.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: stackContainer.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: stackContainer.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: stackContainer.bottomAnchor),
            
            view1.widthAnchor.constraint(equalToConstant: 240.0),
            view1.heightAnchor.constraint(equalToConstant: 160.0),
            
            view2.widthAnchor.constraint(equalTo: view1.widthAnchor),
            view2.heightAnchor.constraint(equalTo: view1.heightAnchor),

            animView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            animView.widthAnchor.constraint(equalToConstant: 160.0),
            animView.heightAnchor.constraint(equalToConstant: 40.0),
            animLeadingConstraint,
            
        ])
        
    }

    @objc func btnTap(_ sender: Any?) -> Void {
        guard let btn = sender as? UIButton else {
            return
        }
        if btn.currentTitle == "Fix 1" {
            fixedApproachOne()
        } else if btn.currentTitle == "Fix 2" {
            fixedApproachTwo()
        } else {
            defaultApproach()
        }
    }
    
    func defaultApproach() -> Void {

        // nothing happening between
        //  hide / show arranged subview
        // and
        //  the animation block

        // so, this is the START of the "animation"
        self.view2.isHidden.toggle()

        runAnim()
        
    }

    func fixedApproachOne() -> Void {

        // start the animation
        runAnim()
        
        // hide / show arranged subview AFTER animation block
        self.view2.isHidden.toggle()
        
    }
    
    func fixedApproachTwo() -> Void {

        // hide / show arranged subview
        self.view2.isHidden.toggle()
        
        // force layout update
        self.view.setNeedsLayout()
        self.view.layoutIfNeeded()
        
        // now start the animation
        runAnim()
        
    }
    
    func runAnim() -> Void {
        // animate constraint change
        self.animLeadingConstraint.isActive.toggle()
        self.animTrailingConstraint.isActive = !self.animLeadingConstraint.isActive
        UIView.animate(withDuration: 0.5, animations: {
            self.view.layoutIfNeeded()
        })
    }
    
}

页面效果如下所示:

enter image description here


我会接受这个答案。我的问题更深层次,是由于我从设计团队使用的自定义UI组件引起的。但这很清晰,对于我认为的问题会有所帮助。谢谢。 - rickrvo
很好,如果这能帮助您解决问题,那就太棒了。以后请在提问时提供尽可能多的信息,这样您就更有可能得到有用的答案。 - DonMag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接