Skip to content

Instantly share code, notes, and snippets.

@Sherlouk
Created September 25, 2021 15:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sherlouk/290ba52892e21dd2bb22827b2af86e78 to your computer and use it in GitHub Desktop.
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
//
// 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
@Sherlouk
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment