Staggered Grid
Updated:
🔷 Staggered Grid
// StaggeredGrid.swift
import SwiftUI
// Custom View Builder
// T -> is to hold the identifiable collection of data..
struct StaggeredGrid<Content: View, T: Identifiable>: View where T: Hashable {
// MARK: - PROPERTY
// It will return each object from collection to build view..
var content: (T) -> Content
var list: [T]
var columns: Int
var showIndicators: Bool
var spacing: CGFloat
init(columns: Int , showIndicators: Bool = false, spacing: CGFloat = 10, list: [T], @ViewBuilder content: @escaping (T)->Content) {
self.content = content
self.list = list
self.spacing = spacing
self.showIndicators = showIndicators
self.columns = columns
}
// MARK: - BODY
var body: some View {
ScrollView(.vertical, showsIndicators: showIndicators) {
HStack (alignment: .top){
ForEach(setUpList(), id: \.self) { columnsData in
// For Optimized Using LazyStack..
LazyVStack (spacing: spacing) {
ForEach(columnsData) { object in
content(object)
}
}
}
} //: HSTACK
} //: SCROLL
// Only vertical padding..
// horizontal padding will be user's optional..
.padding(.vertical)
}
// MARK: - FUNCTION
// Staggered Grid Function
func setUpList() -> [[T]] {
// Creating empty sub arrays of columns count...
var gridArray: [[T]] = Array(repeating: [], count: columns)
// spiliting array for Vstack oriented View...
var currentIndex: Int = 0
for object in list {
gridArray[currentIndex].append(object)
// increasing index count..
// and resetting if overbounds the columns count...
if currentIndex == (columns - 1) {
currentIndex = 0
} else {
currentIndex += 1
}
}
return gridArray
}
}
// MARK: - PREVIEW
struct StaggeredGrid_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Home.swift
import SwiftUI
struct Home: View {
// MARK: - PROPERTY
@State var posts: [Post] = []
// To show dynamic..
@State var columns: Int = 2
// Smooth hero effect..
@Namespace var animation
// MARK: - BODY
var body: some View {
NavigationView {
StaggeredGrid(columns: columns, list: posts, content: { post in
// Post Card View..
PostCardView()
.matchedGeometryEffect(id: post.id, in: animation)
})
.padding(.horizontal)
.navigationTitle("Staggered Grid")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
columns += 1
} label: {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
columns = max(columns - 1, 0)
} label: {
Image(systemName: "minus")
}
}
}
// animation..
.animation(.easeInOut, value: columns)
}
.onAppear {
for _ in 1...10 {
posts.append(Post(imageURL: "https://picsum.photos/200"))
}
}
}
}
// MARK: - PREVIEW
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
// Since we declared T as Identifiable..
// So we need to pass Idenfiable confirm collection/Array...
struct PostCardView: View {
let url = URL(string: "https://picsum.photos/200")
var body: some View {
AsyncImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(20)
} placeholder: {
ProgressView()
}
}
}
🗃 Reference
SwiftUI 3.0: Staggered Grid With Matched Geometry Effect - Xcode 13 - WWDC 2021 - https://www.youtube.com/watch?v=VrwINubmq5g
Leave a comment