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 определяется высотой самой высокой ячейки. Согласно приведенному вами примеру, источник данных будет показывать меньшую высоту только в том случае, если вначале он имеет небольшой размер.
Если первый рендеринг не будет знать, что существуют разные высоты, используйте большее значение в качестве высоты.
Ожидается ли ваше ожидаемое поведение пользовательского интерфейса, когда высота будет автоматически переключаться? Или используйте самую высокую высоту с самого начала.