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

将高度可变的UIHostingController嵌入UIStackView后布局不更新的问题

问题:SwiftUI视图嵌入UIKit的UIStackView后,动态高度无法被适配

我在尝试将SwiftUI视图嵌入现有UIKit视图时遇到问题:SwiftUI视图高度可动态变化,但UIStackView无法适配其新尺寸。我创建了一个测试项目来复现该问题。

测试代码

自定义Button代码

class TestButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupButton()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupButton()
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupButton() {
        setTitleColor(.white, for: .normal)
        backgroundColor = .red
        titleLabel?.font = .boldSystemFont(ofSize: 25)
        layer.cornerRadius = 10
    }
}

SwiftUI视图代码

struct TestSwiftUIView: View {
    @State var text: [String] = ["This is a line of text"]
    var body: some View {
        VStack {
            ForEach(text, id: \.self) { text in
                Text(text)
            }
            Button {
                text.append("New line")
            } label: {
                Text("Add line")
            }
            .padding()
            .background(Color.red)
        }
        .foregroundColor(.white)
        .background(Color.green)
    }
}

ViewController代码

class ViewController: UIViewController {
    
    var titleLabel = UILabel()
    var stackView = UIStackView()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureTitleLabels()
        configureStackView()
    }
    
    func configureStackView() {
        view.addSubview(stackView)
        
        stackView.axis = .vertical
        stackView.distribution = .fillEqually
        stackView.spacing = 20
        
        addButtonsToStackView()
        setStackViewConstraints()
        
        let hostingController = UIHostingController(rootView: TestSwiftUIView())
        stackView.insertArrangedSubview(hostingController.view, at: 3)
    }
    
    func addButtonsToStackView() {
        let numberOfButtons = 5
        
        for i in 1...numberOfButtons {
            let button = TestButton()
            button.setTitle("\(i)", for: .normal)
            stackView.addArrangedSubview(button)
        }
    }
    
    func setStackViewConstraints() {
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 30)
            .isActive = true
        stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50).isActive = true
        stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50).isActive = true
        stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30).isActive = true
    }
    
    func configureTitleLabels() {
        view.addSubview(titleLabel)
        titleLabel.text = "Test project"
        titleLabel.font = .systemFont(ofSize: 30)
        titleLabel.textAlignment = .center
        titleLabel.numberOfLines = 0
        titleLabel.adjustsFontSizeToFitWidth = true
        
        setTitleLabelConstaints()
    }
    
    func setTitleLabelConstaints() {
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20)
            .isActive = true
        titleLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
        titleLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
    }
}

问题现象

点击SwiftUI视图中的“Add line”按钮后,视图高度会扩展,但UIStackView没有适配这个变化,导致内容被挤压重叠。

已尝试的解决方案(无效)

我曾尝试使用layoutIfNeeded()解决问题,给SwiftUI视图添加了回调:

let callback: () -> Void

修改按钮点击逻辑:

Button {
    text.append("New line")
    callback()
} label: {
    Text("Add line")
        .font(.system(size: 18, weight: .bold, design: nil))
}

在ViewController中调用:

let hostingController = UIHostingController(rootView: TestSwiftUIView(callback: {
            self.stackView.subviews.forEach { view in
                view.sizeToFit()
                view.layoutIfNeeded()
            }
        }))

但这种方法没有效果。

如何让UIStackView为高度扩展的SwiftUI视图预留足够空间?


解答

核心问题原因

你的UIStackView设置了distribution = .fillEqually,这个属性会强制所有排列的子视图拥有相同尺寸,无视子视图自身的内容变化。另外,UIHostingController的view默认没有正确将内部SwiftUI视图的动态尺寸约束传递给UIKit布局系统。

解决步骤

  1. 修改UIStackView的分布属性
    将stackView.distribution = .fillEqually改为stackView.distribution = .fill,或根据需求选择.equalSpacing/.equalCentering,让StackView根据子视图实际尺寸分配空间。

  2. 给UIHostingController的view添加正确约束
    UIHostingController的view需要明确的尺寸约束,让UIKit感知SwiftUI视图的动态变化。设置其宽度匹配StackView,高度自适应内容:

let hostingController = UIHostingController(rootView: TestSwiftUIView())
addChild(hostingController)
hostingController.didMove(toParent: self) // 必须调用以保证生命周期正确
stackView.insertArrangedSubview(hostingController.view, at: 3)

// 添加约束,让hostingView宽度匹配stackView,高度自适应
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    hostingController.view.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
    hostingController.view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
])
  1. 优化SwiftUI视图的布局优先级
    给SwiftUI的VStack添加fixedSize(horizontal: false, vertical: true),确保它能正确计算自身动态高度:
struct TestSwiftUIView: View {
    @State var text: [String] = ["This is a line of text"]
    var body: some View {
        VStack {
            ForEach(text, id: \.self) { text in
                Text(text)
            }
            Button {
                text.append("New line")
            } label: {
                Text("Add line")
            }
            .padding()
            .background(Color.red)
        }
        .foregroundColor(.white)
        .background(Color.green)
        .fixedSize(horizontal: false, vertical: true) // 添加此行
    }
}

完整修改后的ViewController关键代码

func configureStackView() {
    view.addSubview(stackView)
    
    stackView.axis = .vertical
    stackView.distribution = .fill // 修改此处
    stackView.spacing = 20
    
    addButtonsToStackView()
    setStackViewConstraints()
    
    let hostingController = UIHostingController(rootView: TestSwiftUIView())
    addChild(hostingController)
    hostingController.didMove(toParent: self)
    stackView.insertArrangedSubview(hostingController.view, at: 3)
    
    hostingController.view.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        hostingController.view.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
        hostingController.view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
    ])
}

修改后,SwiftUI视图添加新内容时高度会自动更新,UIStackView会重新布局并为其分配足够空间,不会再出现内容挤压问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:13:10