Пользовательская структура сетки не передает размер GridItems в SwiftUI
Я пытаюсь создать новую сеточную структуру в SWiftUI для представления полей с переменными размерами. В этой связи я использую несколько
LazyVGrid
с в
HStack
и иметь условие для отображения моих элементов данных в правильном порядке. Это представление работает отдельно, адаптирует количество столбцов к размеру экрана, но при использовании его в другом
VStack
например, его размер не вычисляется должным образом, и следующие представления оказываются под сеткой, а не под ней. Вот код, который я использовал для сетки:
struct StaggeredGrid<Element, Content>: View where Content: View {
var preferredItemWidth: CGFloat
var data: [Element] = []
var content: (Element) -> Content
let column = [GridItem(.flexible())]
init(preferredItemWidth: CGFloat, data: [Element], @ViewBuilder content: @escaping (Element) -> Content) {
self.preferredItemWidth = preferredItemWidth
self.data = data
self.content = content
}
var body: some View {
GeometryReader { geometry in
HStack(alignment: .top, spacing: 20) {
ForEach(1...Int(geometry.size.width / preferredItemWidth), id: \.self) { number in
LazyVGrid(columns: column, spacing: 20) {
ForEach(0..<data.count, id: \.self) { index in
if (index+1) % Int(geometry.size.width / preferredItemWidth) == number % Int(geometry.size.width / preferredItemWidth) {
content(data[index])
}
}
}
}
}
.padding([.horizontal])
}
}
}
И предварительный просмотр, чтобы показать поведение:
struct StaggeredGrid_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
VStack {
StaggeredGrid(preferredItemWidth: 160, data: (1...10).map{"Item \($0)"}) { item in
Text(item)
.foregroundColor(.blue)
}
Text("I should be below the grid")
}
}
}
}
Вот изображение превью. Неправильный внешний вид в VStack
Заранее благодарим вас за любую помощь или подсказку об этом поведении, которое я не понимаю.
1 ответ
Я цитирую Asperi : «ScrollView не имеет собственного размера, а GeometryReader не имеет собственного размера, поэтому вы столкнулись с проблемой куриного яйца, т.е. никто не знает размер для рендеринга, поэтому он свернут. У вас должен быть определенный размер кадра для элементов внутри ScrollView."
Здесь вы должны установить высоту вашего
StaggeredGrid
, или
GeometryReader
это содержит. В вашем случае его высота, конечно, зависит от высоты его содержимого (то есть HStack). Вы можете прочитать эту высоту (например, высоту фона / наложения) с помощью Reader. И используйте его, чтобы определить размер кадра вашего GeometryReader
Например :
struct StaggeredGrid<Element, Content>: View where Content: View {
var preferredItemWidth: CGFloat
var data: [Element] = []
var content: (Element) -> Content
init(preferredItemWidth: CGFloat, data: [Element], @ViewBuilder content: @escaping (Element) -> Content) {
self.preferredItemWidth = preferredItemWidth
self.data = data
self.content = content
}
@State private var gridHeight: CGFloat = 100
var body: some View {
GeometryReader { geometry in
HStack(alignment: .top, spacing: 20) {
ForEach(1...Int(geometry.size.width / preferredItemWidth), id: \.self) { number in
LazyVStack(spacing: 20) {
ForEach(0..<data.count, id: \.self) { index in
if (index+1) % Int(geometry.size.width / preferredItemWidth) == number % Int(geometry.size.width / preferredItemWidth) {
content(data[index])
}
}
}
}
}
.overlay(GeometryReader { proxy in
Color.clear.preference(
key: HeightPreferenceKey.self,
value: proxy.size.height
)
})
.onPreferenceChange(HeightPreferenceKey.self) {
gridHeight = $0
}
.padding([.horizontal])
}
.frame(height: gridHeight)
}
}
private struct HeightPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat,
nextValue: () -> CGFloat) {
value = nextValue()
}
}