Created
September 25, 2021 15:00
-
-
Save Sherlouk/290ba52892e21dd2bb22827b2af86e78 to your computer and use it in GitHub Desktop.
An example demonstrating a LazyVGrid with all tiles in a row sharing the same height
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// LazyEqualVGrid.swift | |
// | |
// Created by James Sherlock on 25/09/2021. | |
// | |
import SwiftUI | |
struct LazyEqualVGrid<Content: View, Data: Identifiable>: View { | |
@State private var rowHeightLookup: [Int: CGFloat] = [:] | |
let columns: [GridItem] | |
let alignment: HorizontalAlignment | |
let spacing: CGFloat? | |
let pinnedViews: PinnedScrollableViews | |
let data: [IdentifiableData<Data>] | |
let content: (Data, CGFloat?) -> Content | |
init( | |
columns: [GridItem], | |
alignment: HorizontalAlignment = .center, | |
spacing: CGFloat? = nil, | |
pinnedViews: PinnedScrollableViews = .init(), | |
dataSource: [Data], | |
@ViewBuilder content: @escaping (Data, CGFloat?) -> Content | |
) { | |
self.columns = columns | |
self.alignment = alignment | |
self.spacing = spacing | |
self.pinnedViews = pinnedViews | |
self.data = (0..<dataSource.count).map { | |
IdentifiableData(cellIndex: $0, data: dataSource[$0]) | |
} | |
self.content = content | |
} | |
var body: some View { | |
LazyVGrid( | |
columns: columns, | |
alignment: alignment, | |
spacing: spacing, | |
pinnedViews: pinnedViews | |
) { | |
ForEach(data) { data in | |
let rowOffset = data.cellIndex / columns.count | |
content(data.data, rowHeightLookup[rowOffset]) | |
.overlay(DetermineEqualGridRowHeight(rowOffset: rowOffset)) | |
} | |
} | |
.onPreferenceChange(EqualGridRowPreferenceKey.self) { newValue in | |
DispatchQueue.main.async { | |
rowHeightLookup = newValue | |
} | |
} | |
} | |
} | |
// MARK: - Enumerated Identifiable Data Source | |
struct IdentifiableData<Data: Identifiable>: Identifiable { | |
let cellIndex: Int | |
let data: Data | |
var id: Data.ID { | |
data.id | |
} | |
} | |
// MARK: - Preference Key | |
// We use a preference key in order to store the maximum tile height per row. | |
// This is then used to refresh the grid with the correct heights. | |
struct EqualGridRowPreferenceKey: PreferenceKey { | |
static var defaultValue: [Int: CGFloat] = [:] | |
static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) { | |
nextValue().forEach { row, newValue in | |
value[row] = max(value[row] ?? 0, newValue) | |
} | |
} | |
} | |
struct DetermineEqualGridRowHeight: View { | |
typealias Key = EqualGridRowPreferenceKey | |
let rowOffset: Int | |
var body: some View { | |
GeometryReader { proxy in | |
Color.clear.anchorPreference(key: Key.self, value: .bounds) { anchor in | |
[ rowOffset: proxy[anchor].size.height ] | |
} | |
} | |
} | |
} | |
// MARK: - Preview | |
#if DEBUG | |
struct LazyEqualVGrid_Previews: PreviewProvider { | |
struct PreviewData: Identifiable, ExpressibleByStringLiteral { | |
var id: String { value } | |
let value: String | |
init(stringLiteral value: String) { | |
self.value = value | |
} | |
} | |
static var previews: some View { | |
LazyEqualVGrid( | |
columns: [ | |
GridItem(.fixed(100)), | |
GridItem(.fixed(100)), | |
GridItem(.fixed(100)), | |
], | |
dataSource: [ | |
"Lorem ipsum dolor", | |
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | |
"Lorem ipsum dolor sit amet, consectetur", | |
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus maximus sem dolor, venenatis vehicula est vulputate sed.", | |
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus maximus sem dolor, venenatis vehicula est vulputate sed. Nunc semper metus at risus hendrerit facilisis.", | |
"Lorem ipsum dolor sit amet,", | |
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | |
"Lorem ipsum dolor sit amet, consectetur", | |
] as [PreviewData] | |
) { data, height in | |
Text(data.value) | |
.frame(height: height) | |
.background(Color.red) | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Credit where due, the idea for this solution originated from this blog post. I just adapted it the idea to make it work for a VGrid on a per-row basis.