Пользовательская структура сетки не передает размер 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()
    }
}
Другие вопросы по тегам