尝试以编程方式使用嵌套的 Stack 视图制作网格

Posted

技术标签:

【中文标题】尝试以编程方式使用嵌套的 Stack 视图制作网格【英文标题】:Trying to make a grid with nested UIStack views programatically 【发布时间】:2018-04-17 19:11:48 【问题描述】:

我想创建始终适合其父视图大小的“GridView”。所以我的想法是这样的:

将垂直堆栈视图作为主容器。 在垂直堆栈视图内将是水平堆栈视图行。 作为补充,我只想在水平堆栈视图的行中添加按钮。

所以关于如何初始化它的一般纲要如下:

    初始化垂直堆栈视图并将其约束设置为其父视图的前导、尾随、宽度和高度锚点。 根据给定的网格尺寸 (AxB),在这些水平行内创建 A 水平堆栈行和 B 按钮。 将每个水平堆栈的约束设置为等于其父视图的前导、尾随、宽度和高度(主容器堆栈视图)。 将每个按钮的 autoresizingMask 设置为 flexibleWidth 和 flexibleHeight。

这是我目前用于初始化“GridView”的当前逻辑,我正在使用它来实现我的实现。现在我遇到的主要问题是水平堆栈视图不是垂直堆叠,而是实际上最终只是相互重叠。

(1x1) Overlap

如果我将按钮 just 添加到主容器中,它将垂直堆叠而不会出现问题。

(1x1) Vertical Buttons

似乎将水平堆栈放入垂直堆栈会导致问题。有什么想法和建议吗?我曾想过只使用表格视图来创建网格,但这个问题让我很好奇,我真的很想知道为什么会这样。

这是我的代码实现:

func createGrid(x: Int, y: Int, rootView: UIView) 
    //Init stack view.
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.alignment = .fill
    stackView.distribution = .fillEqually


    for i in (1...x) 
        let hStackView = UIStackView()
        hStackView.axis = .horizontal
        hStackView.alignment = .fill
        hStackView.distribution = .fillEqually

        for _ in (1...y) 
            let button = WordBoxView()
            button.setTitle("\(i)", for: .normal)
            hStackView.addArrangedSubview(button)
        

        let button = WordBoxView()
        button.setTitle("\(i)", for:.normal)
        hStackView.addArrangedSubview(button)

        //Add horizontal row stack to vertical parent stack.
        stackView.addSubview(hStackView)
        fitParentLayout(hStackView, parentView: stackView)
    

    rootView.addSubview(stackView)

    //setup stack view bounds
    stackView.translatesAutoresizingMaskIntoConstraints = false
    fitParentLayout(stackView, parentView: rootView)


func fitParentLayout(_ child: UIView, parentView: UIView) 

    child.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        parentView.leadingAnchor.constraint(equalTo: child.leadingAnchor),
        parentView.trailingAnchor.constraint(equalTo: child.trailingAnchor),
        parentView.widthAnchor.constraint(equalTo: child.widthAnchor),
        parentView.heightAnchor.constraint(equalTo: child.heightAnchor)])

【问题讨论】:

我认为你可以用集合视图来做到这一点,你试过了吗? 你真的不应该使用addSubview添加UIStackView子视图,即使用addArrangedSubview添加hStackView到垂直堆栈视图。另外,不要添加约束(无需使用fitParentLayout 将水平堆栈视图添加到垂直堆栈视图)。 【参考方案1】:

我在您的代码中发现了 2 个问题:

    您已添加leading, trailing, widthheight 约束,但它不会确定您的视图的y 原点。所以分配top。 (前导和尾随将自动确定宽度,因此无需给出宽度限制)

    你只需要给外部UIStackView一个约束,内部堆栈视图会根据外部堆栈视图的属性自动获取帧。

将您的代码更新为:

func createGrid(x: Int, y: Int, rootView: UIView) 
    //Init stack view.
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.alignment = .fill
    stackView.distribution = .fillEqually
    stackView.spacing = 10
    
    for i in 1...x 
        let hStackView = UIStackView()
        hStackView.axis = .horizontal
        hStackView.alignment = .fill
        hStackView.distribution = .fillEqually
        hStackView.spacing = 10
        
        for _ in 1...y 
            let button = UIButton()
            button.backgroundColor = .red
            button.setTitle("\(i)", for: .normal)
            hStackView.addArrangedSubview(button)
        

        //Add horizontal row stack to vertical parent stack.
        stackView.addArrangedSubview(hStackView)
    
    
    rootView.addSubview(stackView)
    
    //setup stack view bounds
    fitParentLayout(stackView, parentView: rootView)



func fitParentLayout(_ child: UIView, parentView: UIView) 
    child.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        parentView.leadingAnchor.constraint(equalTo: child.leadingAnchor),
        parentView.trailingAnchor.constraint(equalTo: child.trailingAnchor),
        parentView.topAnchor.constraint(equalTo: child.topAnchor),
        parentView.heightAnchor.constraint(equalTo: child.heightAnchor)])

【讨论】:

这行得通,在 fitParentLayout() 而不是 parentView.heightAnchor.constraint(equalTo: child.heightAnchor)]) 我使用了 parentView.bottomAnchor.constraint(equalTo: child.bottomAnchor)])。跨度> 【参考方案2】:

你说

将每个水平堆栈的约束设置为其父视图(主容器堆栈视图)的前导、尾随、宽度和高度。

您不希望行(水平堆栈视图)的高度等于主容器堆栈视图的高度。设置高度限制可能是你的麻烦的原因。

移除此约束:

parentView.heightAnchor.constraint(equalTo: child.heightAnchor)

您也不需要父/子宽度限制,但那是无害的。

【讨论】:

你是对的,我使用了错误的约束,我没有意识到这一点。我调整了我的代码以匹配@DonMag 的建议。【参考方案3】:

几个“哎呀”错误......

for i in (1...x) 循环结束时:

   //Add horizontal row stack to vertical parent stack.
   stackView.addSubview(hStackView)

应该是:

   //Add horizontal row stack to vertical parent stack.
   stackView.addArrangedSubview(hStackView)

而且,您不需要(也不应该这样做)以下行

   fitParentLayout(hStackView, parentView: stackView)  

因为您正在使用垂直堆栈视图的.alignment.distribution 来控制子水平堆栈视图的大小/位置。

而且,您的fitParentLayout() 已交换视图。应该是:

func fitParentLayout(_ child:UIView, parentView:UIView)

    child.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        child.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
        child.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
        child.topAnchor.constraint(equalTo: parentView.topAnchor),
        child.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)])

应该可以的。

【讨论】:

啊,我应该一直使用 topAnchor 和 bottomAnchor 而不是宽度和高度锚约束。

以上是关于尝试以编程方式使用嵌套的 Stack 视图制作网格的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式删除数据网格视图中的行标题

嵌套堆栈视图:以编程方式附加子堆栈视图时,子堆栈视图不在其父堆栈视图内

Android 编程:如何以网格方式以编程方式创建各种视图类型

以编程方式创建网格视图

使用 xamarin.ios c# 以编程方式添加嵌套的堆栈视图

以编程方式将值添加到网格视图页脚