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

为何UIStackView的fillProportionally布局属性未按预期工作?

UIStackView.fillProportionally布局下按钮高度不一致问题分析与解决

问题现象

在垂直方向的UIStackView中放置两个标题、字体、字号完全一致的按钮,设置distribution = .fillProportionally时,两个按钮高度出现明显差异(如94pt和86pt);但使用.fillEqually布局时高度完全一致,且将StackView的spacing设为0时,.fillProportionally布局下两个按钮高度也恢复一致。

通过代码打印帧高度验证该现象:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    print("button 1 height: \(button1.frame.height)")
    print("button 2 height: \(button2.frame.height)")
}

终端输出:

button 1 height: 94.0
button 2 height: 86.0
button 1 height: 94.0
button 2 height: 86.0

复现代码

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    
    let buttonOne = UIButton()
    buttonOne.titleLabel?.font = UIFont(name: "Marker Felt Wide", size: 17.0)
    buttonOne.backgroundColor = .red
    buttonOne.setTitle("Pirate Handbook", for: .normal)
    
    let buttonTwo = UIButton()
    buttonTwo.titleLabel?.font = UIFont(name: "Marker Felt Wide", size: 17.0)
    buttonTwo.backgroundColor = .blue
    buttonTwo.setTitle("Pirate Handbook", for: .normal)
    
    buttonOne.translatesAutoresizingMaskIntoConstraints = false
    buttonTwo.translatesAutoresizingMaskIntoConstraints = false
    
    let stackView = UIStackView(arrangedSubviews: [buttonTwo, buttonOne])
    stackView.axis = .vertical
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.distribution = .fillProportionally
    stackView.alignment = .fill
    stackView.spacing = 8
    stackView.backgroundColor = .green
    
    view.addSubview(stackView)
    
    NSLayoutConstraint.activate([
        stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 110),
        stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 23),
        stackView.widthAnchor.constraint(equalToConstant: 154),
        stackView.heightAnchor.constraint(equalToConstant: 188)
    ])
  }

  override func viewDidLayoutSubviews() {
    view.subviews.first?.subviews.forEach {
      print("height \($0.frame.height)")
    }
  }
}

代码运行后终端输出:

height 0.0 
height 0.0
height 84.33333333333333
height 95.66666666666667

原因解析

fillProportionally的核心逻辑是按子视图intrinsic content size的比例分配剩余空间。当StackView设置固定高度且包含spacing时,系统会先扣除spacing,再将剩余空间按比例分配给子视图。

尽管两个按钮的标题和字体完全一致,但UIButton的intrinsic content size计算不仅依赖标题,还受内部默认的content inset、title edge inset等属性影响,可能存在极细微的浮点精度差异。扣除spacing后,剩余空间的比例分配会放大这个微小差异,最终导致按钮高度出现明显差值。

而fillEqually是直接将剩余空间平均分配,不受intrinsic size比例影响,因此高度一致;当spacing设为0时,剩余空间分配无额外扣除项,微小的intrinsic size差异被抵消,高度也能保持一致。

解决方案

  • 使用.fillEqually替代:如果需求是子视图高度完全一致,.fillEqually是更直接可靠的选择,无需依赖intrinsic size计算。
  • 添加固定高度约束:给两个按钮添加明确的高度约束(如button.heightAnchor.constraint(equalToConstant: 94)),强制高度一致,覆盖fillProportionally的比例分配逻辑。
  • 统一intrinsic size:手动设置两个按钮相同的content inset、title edge inset,消除intrinsic size的细微差异;或取消StackView的固定高度,让其自适应内容高度。

内容的提问来源于stack exchange,提问作者iosdev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 20:42:30