首页 > 解决方案 > 是否可以修改 LazyVGrid/LazyHGrid (SwiftUI 5.5) 中项目的位置?

问题描述

我对 Swift 比较陌生,并且有一个网格布局问题:我目前正在使用带有自适应网格项目和单列的 LazyHGrid 来灵活地布局项目,而无需预先确定列数。我根据要显示的项目数设置 maxWidth 网格布局和 maxWidth 来确定显示的列数

我在下面的每个场景中试图完成的(使用一个也可以扩大规模的解决方案)基本上是通过将项目从位置“”移到位置“⚪️”来调整下面示例中的位置,消除“漂浮的孤儿”项目没有拥抱边缘。“⚫️”位置已经正确。

约束

我试过的

例子

/* short */
___________
      ⚫️⚫️|
      ⚪️|

/* tall */
___________
        ⚫️|
        ⚫️|
        ⚫️|
/* short */
___________
      ⚫️⚫️|
      ⚫️⚫️|

/* tall */
___________    ___________
      ⚫️⚫️|          ⚫️⚫️| // NOTE: I *could* constrain the height in this case
      ⚪️|          ⚫️⚫️| // to achieve the 2x2 grid but would rather not
      ⚪️|
/* short */
___________
    ⚫️⚫️⚫️|
    ⚫️⚪️|

/* tall */
___________
      ⚫️⚫️|
      ⚫️⚫️|
      ⚪️|
/* short */
___________
    ⚫️⚫️⚫️|
    ⚫️⚫️⚫️|

/* tall */
___________
      ⚫️⚫️|
      ⚫️⚫️|
      ⚫️⚫️|
/* short */
___________
  ⚫️⚫️⚫️⚫️|
  ⚫️⚫️⚪️|

/* tall */
___________
      ⚫️⚫️|
      ⚫️⚫️|
      ⚫️⚫️|
      ⚪️|
let items = filterItems()
let iconCount = CGFloat(items.count)
let iconSize: CGFloat = 18
let spacing: CGFloat = 2

// NOTE: `iconCount / 2` because the min-height of the parent 
// only fits two items. The current item count is 7 and it's
// unlikely that it'd ever do more than double.

let cols = CGFloat(iconCount / 2).rounded(.up)
let maxWidth = calcWidth(for: cols, iconSize: iconSize, spacing: spacing)
let minWidth = cols < 2 ? maxWidth : calcWidth(for: cols - 1, iconSize: iconSize, spacing: spacing)

LazyHGrid(
  rows: Array(
    repeating: GridItem(
      .adaptive(minimum: iconSize, maximum: iconSize),
      spacing: spacing,
      alignment: .center
    ),
    count: 1
  ),
  alignment: .center,
  spacing: spacing
) {
  ForEach(0..<items.count, id: \.self) { n in

    ...item views...

  }
}
.frame(minWidth: minWidth, maxWidth: maxWidth)

我的大脑一直在寻找一个类似于 CSS 的 FlexBox 的 Swift 概念,justify-content: flex-end; align-items: flex-start但我觉得应该有一个更灵活/更快捷的解决方案来解决这个问题,而我只是错过了?

(网格目前嵌套在一个 HStack 中,并用一个间隔将其推到其父级的顶部尾角,有效地完成了align-items: flex-start上面提到的 Flexbox 解决方案的一部分,如上图所示)

标签: swiftswiftuiautolayout

解决方案


编辑:布局方向对我有用

您提到这在您的帖子中不起作用,但我刚刚测试了将布局方向设置为从右到左,并且它有效。所有最终项目都向右对齐。也许您正在测试的特定操作系统存在布局错误。完整的操场片段(在 Xcode 13 上测试):

import UIKit
import PlaygroundSupport
import SwiftUI
import Foundation

let iconCount: CGFloat = 4
let iconSize: CGFloat = 18
let spacing: CGFloat = 2
let cols: CGFloat = 2
let maxWidth = calcWidth(for: cols, iconSize: iconSize, spacing: spacing)
let minWidth = cols < 2 ? maxWidth : calcWidth(for: cols - 1, iconSize: iconSize, spacing: spacing)

func calcWidth(for cols: CGFloat, iconSize: CGFloat, spacing: CGFloat) -> CGFloat {
    return iconSize * cols + spacing * cols
}

PlaygroundPage.current.liveView = UIHostingController(rootView: {
    TabView {
        HStack {
            LazyHGrid(
              rows: Array(
                repeating: GridItem(
                  .adaptive(minimum: iconSize, maximum: iconSize),
                  spacing: spacing,
                  alignment: .center
                ),
                count: 1
              ),
              alignment: .center,
              spacing: spacing
            ) {
              ForEach(0..<Int(iconCount), id: \.self) { n in
                  Text("B")
                      .frame(width: iconSize, height: iconSize)
                      .border(.red)
              }
            }
            .frame(minWidth: minWidth, maxWidth: maxWidth)
            .background(Color.green)
            .frame(height: iconSize * 3 + spacing * 2)
            .environment(\.layoutDirection, .rightToLeft)
            // I also tried this, but it isn't needed
            //.flipsForRightToLeftLayoutDirection(true)
        }
    }
}())

在此处输入图像描述

原答案:

这不是理想的解决方案,但一种选择是使用 3D 旋转来翻转 Y 轴上的布局。我在 Playgrounds 中使用了一个简单的文本视图作为示例:

LazyHGrid(
  rows: Array(
    repeating: GridItem(
      .adaptive(minimum: iconSize, maximum: iconSize),
      spacing: spacing,
      alignment: .center
    ),
    count: 1
  ),
  alignment: .center,
  spacing: spacing
) {
  ForEach(0..<Int(iconCount), id: \.self) { n in
      Text("B")
          .frame(width: iconSize, height: iconSize)
          .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
  }
}
.frame(minWidth: minWidth, maxWidth: maxWidth)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))

在此处输入图像描述


推荐阅读