iOS15 UIButton Configuration在UIStackView中隐藏时的异常水平收缩动画问题
问题现象
使用iOS 15引入的UIButton Configuration创建按钮时,将其放入垂直轴的UIStackView中,执行隐藏动画会出现按钮向一侧横向收缩的异常效果;而使用传统system类型按钮时,仅会出现垂直方向的收缩动画。此外,StackView中仅包含单个system按钮时也会出现同样的横向收缩问题,但包含两个system按钮时表现正常。
可复现问题的代码如下:
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()
问题根源
- UIButton Configuration的布局特性:使用Configuration的按钮内部约束逻辑与传统system按钮不同,其intrinsicContentSize在布局动画过程中会参与横向尺寸调整,导致StackView在隐藏按钮时触发横向收缩动画。
- StackView的布局逻辑:当垂直轴StackView中仅包含一个arranged子视图时,StackView会根据子视图的intrinsicContentSize动态调整自身宽度,进而触发横向动画;当存在多个子视图时,子视图之间的宽度约束会互相维持,避免横向尺寸变化。
解决方案
方案1:简化动画逻辑,避免多属性冲突
移除手动修改alpha的逻辑,仅通过isHidden触发布局动画,利用UIKit原生的布局动画机制实现垂直收缩效果:
private var buttonAction: UIAction { UIAction { [weak self] _ in guard let toggleElement = self?.contentStackView.arrangedSubviews[0] else { return } UIView.animate(withDuration: 1) { toggleElement.isHidden.toggle() } } }
该方案通过让UIKit自动处理布局动画,避免了alpha动画与布局动画的叠加冲突,同时Configuration按钮的内部约束会正确响应垂直方向的收缩。
方案2:固定按钮宽度,锁定横向尺寸
给Configuration按钮添加宽度约束,使其与StackView宽度一致,并调整内容优先级确保按钮填充宽度,从而避免横向动画:
- 修改
makeConfigurationButton方法,调整内容优先级:
private func makeConfigurationButton() -> UIButton { let button = UIButton() var config = UIButton.Configuration.filled() config.title = "Configuration Button" button.configuration = config // 降低水平方向内容拥抱优先级,允许按钮填充StackView宽度 button.setContentHuggingPriority(.defaultLow, for: .horizontal) // 提高水平方向内容压缩抵抗优先级,避免宽度被压缩 button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) return button }
- 在添加按钮时,绑定宽度约束:
for _ in 1...2 { let btn = makeConfigurationButton() contentStackView.addArrangedSubview(btn) // 绑定按钮宽度与StackView宽度 btn.widthAnchor.constraint(equalTo: contentStackView.widthAnchor).isActive = true }
方案3:容器视图配合StackView对齐方式
通过给按钮包裹容器视图,设置StackView的alignment为.center,既保证按钮填充宽度,又避免横向动画:
// 修改添加按钮的代码 for _ in 1...2 { let containerView = UIView() containerView.translatesAutoresizingMaskIntoConstraints = false let btn = makeConfigurationButton() containerView.addSubview(btn) // 让按钮填充容器视图宽度 NSLayoutConstraint.activate([ btn.topAnchor.constraint(equalTo: containerView.topAnchor), btn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), btn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), btn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) ]) contentStackView.addArrangedSubview(containerView) } // 设置StackView对齐方式为居中 contentStackView.alignment = .center
此时隐藏容器视图时,StackView仅会执行垂直方向的收缩动画,而按钮在容器中始终填充宽度,满足布局需求。
内容的提问来源于stack exchange,提问作者jacksoor
相关产品推荐
相关产品推荐

