是否可以修改 LazyVGrid/LazyHGrid (SwiftUI 5.5) 中项目的位置?
Is it possible to modify the position of items in a LazyVGrid/LazyHGrid (SwiftUI 5.5)?
我对 Swift 比较陌生,有一个网格布局问题:我目前正在使用具有自适应网格项目和单列的 LazyHGrid 来灵活地布置没有预定列数的项目。我通过根据要显示的项目数设置 maxWidth 和 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 概念,但我觉得 应该 是我所缺少的更 Flexy/Swifty 的解决方案吗?
(网格目前嵌套在一个 HStack 中,并用一个间隔符推到它父级的上尾角,有效地完成了上面提到的 Flexbox 解决方案的 align-items: flex-start
部分,如上图)
编辑:布局方向适合我
您提到这在您的 post 中不起作用,但我刚刚测试了将布局方向设置为从右到左,并且它起作用了。所有最终项目都右对齐。也许您正在测试的特定 OS 存在布局错误。完整的操场片段(在 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))
我对 Swift 比较陌生,有一个网格布局问题:我目前正在使用具有自适应网格项目和单列的 LazyHGrid 来灵活地布置没有预定列数的项目。我通过根据要显示的项目数设置 maxWidth 和 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 概念,但我觉得 应该 是我所缺少的更 Flexy/Swifty 的解决方案吗?
(网格目前嵌套在一个 HStack 中,并用一个间隔符推到它父级的上尾角,有效地完成了上面提到的 Flexbox 解决方案的 align-items: flex-start
部分,如上图)
编辑:布局方向适合我
您提到这在您的 post 中不起作用,但我刚刚测试了将布局方向设置为从右到左,并且它起作用了。所有最终项目都右对齐。也许您正在测试的特定 OS 存在布局错误。完整的操场片段(在 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))