如何通过代码实现自适应父视图的嵌套UIStackView网格布局
这确实是实现自适应父视图尺寸GridView的非常实用的思路!我之前用这个方案做过好几个项目的布局,稳定性拉满,下面我把这个思路细化成可落地的步骤和代码示例:
核心设计思路
用垂直UIStackView作为容器承载所有行,每行用水平UIStackView来排列按钮——利用StackView的自动布局特性,不需要手动计算每个按钮的frame,就能轻松实现适配父视图尺寸的网格布局。
具体实现步骤
1. 初始化主垂直StackView并绑定父视图约束
首先创建垂直方向的StackView,核心是让它完全贴合父视图的边界,这样后续所有子视图都会跟着父视图尺寸变化自动调整:
class AdaptiveGridView: UIView { private let verticalStackView: UIStackView = { let stack = UIStackView() stack.axis = .vertical stack.alignment = .fill // 让每行水平StackView占满宽度 stack.spacing = 8 // 行间距,按需调整 stack.translatesAutoresizingMaskIntoConstraints = false return stack }() override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { super.init(coder: coder) setupView() } private func setupView() { addSubview(verticalStackView) // 绑定约束让垂直StackView完全适配父视图 NSLayoutConstraint.activate([ verticalStackView.leadingAnchor.constraint(equalTo: leadingAnchor), verticalStackView.trailingAnchor.constraint(equalTo: trailingAnchor), verticalStackView.topAnchor.constraint(equalTo: topAnchor), verticalStackView.bottomAnchor.constraint(equalTo: bottomAnchor) // 如果你更习惯用width/height约束,也可以替换成下面两行: // verticalStackView.widthAnchor.constraint(equalTo: widthAnchor), // verticalStackView.heightAnchor.constraint(equalTo: heightAnchor) ]) } }
2. 添加行(水平StackView)并填充按钮
接下来实现添加行和按钮的方法,每行的水平StackView要设置distribution = .fillEqually,保证每行的按钮宽度均分:
extension AdaptiveGridView { // 传入每行的按钮标题数组,创建一行 func addRow(withButtonTitles titles: [String]) { let horizontalStackView = UIStackView() horizontalStackView.axis = .horizontal horizontalStackView.distribution = .fillEqually // 按钮均分宽度 horizontalStackView.spacing = 8 // 按钮间距,按需调整 horizontalStackView.translatesAutoresizingMaskIntoConstraints = false // 给每行添加按钮 titles.forEach { title in let button = UIButton(type: .system) button.setTitle(title, for: .normal) button.titleLabel?.numberOfLines = 1 button.titleLabel?.adjustsFontSizeToFitWidth = true // 可以添加点击事件 button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside) horizontalStackView.addArrangedSubview(button) } verticalStackView.addArrangedSubview(horizontalStackView) } @objc private func buttonTapped(_ sender: UIButton) { print("点击了按钮:\(sender.title(for: .normal) ?? "")") } }
3. 使用自定义GridView
在父视图中初始化并使用这个GridView就很简单了:
// 比如在ViewController的viewDidLoad里 let gridView = AdaptiveGridView() gridView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(gridView) // 给GridView设置父视图约束(比如占满整个view的安全区域) NSLayoutConstraint.activate([ gridView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), gridView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), gridView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), gridView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16) ]) // 添加行和按钮 gridView.addRow(withButtonTitles: ["按钮1", "按钮2", "按钮3"]) gridView.addRow(withButtonTitles: ["按钮4", "按钮5"]) gridView.addRow(withButtonTitles: ["按钮6", "按钮7", "按钮8", "按钮9"])
关键优化点
- 如果需要动态调整列数,可以监听父视图的
layoutSubviews方法,根据父视图宽度计算每行能容纳的按钮数量,动态重建行; - 如果按钮需要固定高度,可以给水平StackView添加height约束,或者给按钮设置heightAnchor;
- 可以给StackView添加
layoutMargins和isLayoutMarginsRelativeArrangement = true,实现整体的内边距,避免内容贴边。
内容的提问来源于stack exchange,提问作者antWilder
相关产品推荐
相关产品推荐

