SwiftUI — динамическая высота строки LazyHGrid

Я создаю вертикальный макет, в котором есть горизонтальная прокрутка. Проблема в том, что просмотры вможет иметь разную высоту (в основном из-за динамических текстовых строк), но сетка всегда вычисляет свою высоту на основе первого элемента в сетке:

Я хочу изменить размер этого светло-красного прямоугольника на основе видимых элементов, поэтому, когда видны более мелкие элементы, он должен выглядеть так:

и когда есть большие предметы, это должно выглядеть так:

Это код, который приводит к состоянию на первом изображении:

      struct TestView: PreviewProvider {

    static var previews: some View {
        ScrollView {
            VStack {
                Color.blue
                    .frame(height: 100)
                ScrollView(.horizontal) {
                    LazyHGrid(
                        rows: [GridItem()],
                        alignment: .top,
                        spacing: 16
                    ) {
                        Color.red
                            .frame(width: 64, height: 24)
                        ForEach(Array(0...10), id: \.self) { value in
                            Color.red
                                .frame(width: 64, height: CGFloat.random(in: 32...92))
                        }
                    }.padding()
                }.background(Color.red.opacity(0.3))
                Color.green
                    .frame(height: 100)
            }
        }
    }
}

Что-то похожее на то, что я хочу, может быть достигнуто следующим образом:

      extension View {
    func readSize(edgesIgnoringSafeArea: Edge.Set = [], onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                SwiftUI.Color.clear
                    .preference(key: ReadSizePreferenceKey.self, value: geometryProxy.size)
            }.edgesIgnoringSafeArea(edgesIgnoringSafeArea)
        )
        .onPreferenceChange(ReadSizePreferenceKey.self) { size in
            DispatchQueue.main.async { onChange(size) }
        }
    }
}

struct ReadSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

struct Size: Equatable {
    var height: CGFloat
    var isValid: Bool
}

struct TestView: View {

    @State private var sizes = [Int: Size]()
    @State private var height: CGFloat = 32
    static let values: [(Int, CGFloat)] =
        (0...3).map { ($0, CGFloat(32)) }
        + (4...10).map { ($0, CGFloat(92)) }

    var body: some View {
        ScrollView {
            VStack {
                Color.blue
                    .frame(height: 100)
                ScrollView(.horizontal) {
                    LazyHGrid(
                        rows: [GridItem(.fixed(height))],
                        alignment: .top,
                        spacing: 16
                    ) {
                        ForEach(Array(Self.values), id: \.0) { value in
                            Color.red
                                .frame(width: 300, height: value.1)
                                .readSize { sizes[value.0]?.height = $0.height }
                                .onAppear {
                                    if sizes[value.0] == nil {
                                        sizes[value.0] = Size(height: .zero, isValid: true)
                                    } else {
                                        sizes[value.0]?.isValid = true
                                    }
                                }
                                .onDisappear { sizes[value.0]?.isValid = false }
                        }
                    }.padding()
                }.background(Color.red.opacity(0.3))
                Color.green
                    .frame(height: 100)
            }
        }.onChange(of: sizes) { sizes in
            height = sizes.filter { $0.1.isValid }.map { $0.1.height }.max() ?? 32
        }
    }

}

... но, как вы видите, это немного запаздывает и немного сложно, разве нет лучшего решения? Всем спасибо!

1 ответ

Высота строки в LazyHGrid определяется высотой самой высокой ячейки. Согласно приведенному вами примеру, источник данных будет показывать меньшую высоту только в том случае, если вначале он имеет небольшой размер.
Если первый рендеринг не будет знать, что существуют разные высоты, используйте большее значение в качестве высоты.

Ожидается ли ваше ожидаемое поведение пользовательского интерфейса, когда высота будет автоматически переключаться? Или используйте самую высокую высоту с самого начала.

Другие вопросы по тегам