【问题标题】:Is it possible to modify the position of items in a LazyVGrid/LazyHGrid (SwiftUI 5.5)?是否可以修改 LazyVGrid/LazyHGrid (SwiftUI 5.5) 中项目的位置?
【发布时间】:2021-12-18 23:55:15
【问题描述】:

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

我在下面的每个场景中想要完成的(使用一个也可以扩大规模的解决方案)基本上是通过将项目移出位置“????”来调整下面示例中的位置。并进入位置“⚪️”,消除不拥抱边缘的“浮动孤儿”项目。 “⚫️”的位置已经正确了。

约束

  • 我想最小化这个网格使用的宽度,但不是所有的行都可以容纳更高的堆栈(所以设置一堆脆弱的规则似乎是一个糟糕的解决方案)而且我对设置固定的行项高度不感兴趣消除对动态解决方案的需求
  • 我想避免使用嵌套的 HStack 和 VStack 手动创建网格,并编写一堆额外的逻辑来管理给定父行的高度(通过 GeometryReader 检索)的每一行和每一列的内容。

我的尝试

  • 我查看了 Lazy_Grid 文档,看看是否有可以完成此操作的视图修改器 - 没有遇到(尽管我完全有可能错过了它!)
  • 我认为也许翻转 layoutDirection(从 LTR 到 RTL)可能会做到,但没有这样的运气
  • 我移动了重复的 gridItem 和lazyHGrid 容器的对齐值,但浮动孤儿仍然存在

示例

  • 三个项目:
/* 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 justify-content: flex-end; align-items: flex-start 的 Swift 概念,但我觉得那里应该是一个我只是想念的更灵活/更快捷的解决方案吗?

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

【问题讨论】:

    标签: swift swiftui autolayout


    【解决方案1】:

    编辑:布局方向适合我

    您提到这在您的帖子中不起作用,但我刚刚测试了将布局方向设置为从右到左,并且它有效。所有最终项目都向右对齐。也许您正在测试的特定操作系统存在布局错误。完整的 Playground sn-p(在 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))
    

    【讨论】:

    • 啊,环境变量!我完全忘记了那个。当我尝试flipsForRightToLeftLayoutDirection 时,我的修饰符可能有问题,但我会尝试几个不同的操作系统版本来确保。谢谢!
    猜你喜欢
    • 2023-02-03
    • 2019-10-24
    • 2014-04-11
    • 2022-11-22
    • 1970-01-01
    • 2023-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多