You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS15 UIButton Configuration在UIStackView中隐藏时的异常水平收缩动画问题

解决UIStackView中使用UIButton Configuration时的隐藏动画异常问题

问题现象

使用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()

问题根源

  1. UIButton Configuration的布局特性:使用Configuration的按钮内部约束逻辑与传统system按钮不同,其intrinsicContentSize在布局动画过程中会参与横向尺寸调整,导致StackView在隐藏按钮时触发横向收缩动画。
  2. 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宽度一致,并调整内容优先级确保按钮填充宽度,从而避免横向动画:

  1. 修改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
}
  1. 在添加按钮时,绑定宽度约束:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:15:43