繁体   English   中英

使用 UIStackView 中的配置隐藏 UIButton 时出现奇怪的水平收缩 animation

[英]Weird horizontal shrinking animation when hiding UIButton with Configuration in UIStackView

当使用新的 iOS 15 ConfigurationStackView隐藏在UIButton中时,我遇到了这个奇怪的 animation 问题。 看游乐场:

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    private weak var contentStackView: UIStackView!
    
    override func viewDidLoad() {
        view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
        view.backgroundColor = .white
        
        let contentStackView = UIStackView()
        contentStackView.spacing = 8
        contentStackView.axis = .vertical
        
        for _ in 1...2 {
            contentStackView.addArrangedSubview(makeConfigurationButton())
        }
        
        let button = UIButton(type: .system)
        button.setTitle("Toggle", for: .normal)
        button.addAction(buttonAction, for: .primaryActionTriggered)
        
        view.addSubview(contentStackView)
        view.addSubview(button)
        
        contentStackView.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
            contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        self.contentStackView = contentStackView
    }
    
    private var buttonAction: UIAction {
        UIAction { [weak self] _ in
            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
                guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
                toggleElement.isHidden.toggle()
                toggleElement.alpha = toggleElement.isHidden ? 0 : 1     
                
                self?.contentStackView.layoutIfNeeded()
            }
        }
    }
    
    private func makeSystemButton() -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle("System Button", for: .normal)
        return button
    }
    
    private func makeConfigurationButton() -> UIButton {
        let button = UIButton()
        var config = UIButton.Configuration.filled()
        config.title = "Configuration Button"
        button.configuration = config
        return button
    }
}

PlaygroundPage.current.liveView = MyViewController()

结果为 animation:

隐藏时按钮收缩到一边

但我希望 animation 看起来像这样,其中按钮仅垂直收缩:

预期的按钮隐藏动画

您只需将contentStackView.addArrangedSubview(makeConfigurationButton())换成contentStackView.addArrangedSubview(makeSystemButton()) (makeSystemButton()) 即可在 playground 中进行复制。

我想这与堆栈视图alignment ,将其设置为center给了我所需的 animation,但是按钮不再填充堆栈视图宽度并通过 AutoLayout 设置宽度再次导致相同的 animation .. . 此外,在堆栈视图中只有一个系统按钮会导致同样奇怪的 animation,但为什么两个系统按钮的行为不同? 什么是解决这个问题的好方法?

您应该向按钮添加高度约束并在动画时更新此约束。 我如下编辑您的代码。

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    private weak var contentStackView: UIStackView!

    override func viewDidLoad() {
        view.frame = CGRect(x: 0, y: 0, width: 300, height: 150)
        view.backgroundColor = .white

        let contentStackView = UIStackView()
        contentStackView.spacing = 8
        contentStackView.axis = .vertical

        for _ in 1...2 {
            contentStackView.addArrangedSubview(makeConfigurationButton())
        }

        let button = UIButton(type: .system)
        button.setTitle("Toggle", for: .normal)
        button.addAction(buttonAction, for: .primaryActionTriggered)

        view.addSubview(contentStackView)
        view.addSubview(button)

        contentStackView.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            contentStackView.topAnchor.constraint(equalTo: view.topAnchor),
            contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        self.contentStackView = contentStackView
    }

    private var buttonAction: UIAction {
        UIAction { [weak self] _ in
            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0) {
                guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return }
                toggleElement.isHidden.toggle()
                toggleElement.alpha = toggleElement.isHidden ? 0 : 1
                toggleElement.heightAnchor.constraint(equalToConstant: toggleElement.isHidden ? 0 : 50)
                self?.contentStackView.layoutIfNeeded()
            }
        }
    }

    private func makeSystemButton() -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle("System Button", for: .normal)
        return button
    }

    private func makeConfigurationButton() -> UIButton {
        let button = UIButton()
        var config = UIButton.Configuration.filled()
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 50)
        ])
        config.title = "Configuration Button"
        button.configuration = config
        return button
    }
}

PlaygroundPage.current.liveView = MyViewController()

如您所见,带有 UIStackView 的内置显示/隐藏UIStackView可能很古怪(当您真正了解它时还有很多其他古怪之处)。

看起来,当使用带有UIButton.Configuration的按钮时,按钮的宽度从堆栈视图分配的宽度变为其固有宽度,因为 animation 发生了。

我们可以通过给按钮一个明确的高度限制来解决这个问题——但是,如果我们想使用固有高度(可能事先不知道)怎么办?

不是设置约束,而是设置按钮的内容压缩阻力优先级::

    button.configuration = config

    // add this line
    button.setContentCompressionResistancePriority(.required, for: .vertical)

    return button

而且我们不再获得水平尺寸:

在此处输入图像描述

不过,您会注意到,按钮不会垂直“挤压”……它会“向上推”到堆栈视图的边界之外。

我们可以通过在堆栈视图上设置.clipsToBounds = true来避免这种情况:

在此处输入图像描述

如果这个效果令人满意,那我们就大功告成了。

然而,正如我们所见,按钮仍然没有被“挤压”。 如果是我们想要的视觉效果,我们可以使用自定义的“自我风格化”按钮而不是配置按钮:

在此处输入图像描述

当然,视觉差异很小——仔细观察按钮的文字并没有被挤压。 如果我们真的、真的、真的希望发生这种情况,我们需要为转换设置动画,而不是使用堆栈视图的默认值 animation。

并且......如果我们利用配置的其他一些便利,使用自我风格化的UIButton可能不是一种选择。

如果你想尝试不同之处,这里有一些示例代码:

class ViewController : UIViewController {
    
    var btnStacks: [UIStackView] = []
    
    override func viewDidLoad() {
        
        view.backgroundColor = .systemYellow
        
        let outerStack = UIStackView()
        outerStack.axis = .vertical
        outerStack.spacing = 12
        
        for i in 1...3 {
            let cv = UIView()
            cv.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
            
            let label = UILabel()
            label.backgroundColor = .yellow
            label.font = .systemFont(ofSize: 15, weight: .light)
            
            let st = UIStackView()
            st.axis = .vertical
            st.spacing = 8
            
            if i == 1 {
                label.text = "Original Configuration Buttons"
                for _ in 1...2 {
                    st.addArrangedSubview(makeOrigConfigurationButton())
                }
            }
            if i == 2 {
                label.text = "Resist Compression Configuration Buttons"
                for _ in 1...2 {
                    st.addArrangedSubview(makeConfigurationButton())
                }
            }
            if i == 3 {
                label.text = "Custom Buttons"
                for _ in 1...2 {
                    st.addArrangedSubview(makeCustomButton())
                }
            }

            st.translatesAutoresizingMaskIntoConstraints = false
            cv.addSubview(st)
            NSLayoutConstraint.activate([
                
                label.heightAnchor.constraint(equalToConstant: 28.0),
                
                st.topAnchor.constraint(equalTo: cv.topAnchor),
                st.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
                st.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
                
                cv.heightAnchor.constraint(equalToConstant: 100.0),
                
            ])
            
            btnStacks.append(st)
            
            outerStack.addArrangedSubview(label)
            outerStack.addArrangedSubview(cv)
            outerStack.setCustomSpacing(2.0, after: label)
            
        }
        
        // a horizontal stack view to hold a label and UISwitch
        let ctbStack = UIStackView()
        ctbStack.axis = .horizontal
        ctbStack.spacing = 8
        
        let label = UILabel()
        label.text = "Clips to Bounds"
        
        let ctbSwitch = UISwitch()
        ctbSwitch.addTarget(self, action: #selector(switchChanged(_:)), for: .valueChanged)
        
        ctbStack.addArrangedSubview(label)
        ctbStack.addArrangedSubview(ctbSwitch)
        
        // put the label/switch stack in a view so we can center it
        let ctbView = UIView()
        ctbStack.translatesAutoresizingMaskIntoConstraints = false
        ctbView.addSubview(ctbStack)
        
        // button to toggle isHidden/alpha on the first
        //  button in each stack view
        let button = UIButton(type: .system)
        button.setTitle("Toggle", for: .normal)
        button.backgroundColor = .white
        button.addAction(buttonAction, for: .primaryActionTriggered)
        
        outerStack.addArrangedSubview(ctbView)
        outerStack.addArrangedSubview(button)
        
        outerStack.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(outerStack)
        
        // respect safe-area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),

            ctbStack.topAnchor.constraint(equalTo: ctbView.topAnchor),
            ctbStack.bottomAnchor.constraint(equalTo: ctbView.bottomAnchor),
            ctbStack.centerXAnchor.constraint(equalTo: ctbView.centerXAnchor),

        ])
        
    }
    
    @objc func switchChanged(_ sender: UISwitch) {
        btnStacks.forEach { v in
            v.clipsToBounds = sender.isOn
        }
    }
    
    private var buttonAction: UIAction {
        UIAction { [weak self] _ in
            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0) {
                guard let self = self else { return }
                self.btnStacks.forEach { st in
                    st.arrangedSubviews[0].isHidden.toggle()
                    st.arrangedSubviews[0].alpha = st.arrangedSubviews[0].isHidden ? 0 : 1
                }
            }
        }
    }
    
    private func makeOrigConfigurationButton() -> UIButton {
        let button = UIButton()
        var config = UIButton.Configuration.filled()
        config.title = "Configuration Button"
        button.configuration = config
        return button
    }
    
    private func makeConfigurationButton() -> UIButton {
        let button = UIButton()
        var config = UIButton.Configuration.filled()
        config.title = "Configuration Button"
        button.configuration = config

        // add this line
        button.setContentCompressionResistancePriority(.required, for: .vertical)

        return button
    }
    
    private func makeCustomButton() -> UIButton {
        let button = UIButton()
        button.setTitle("Custom Button", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.setTitleColor(.lightGray, for: .highlighted)
        button.backgroundColor = .systemBlue
        button.layer.cornerRadius = 6
        return button
    }
    
}

看起来像这样:

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM