Grid View
A Pinterest-style masonry grid layout with dynamic column distribution
Preview
Code
1import SwiftUI23struct GridView: View {4 let items: [GridImage] = [5 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),6 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),7 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),8 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),9 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),10 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),11 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),12 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),13 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),14 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),15 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),16 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),17 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),18 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),19 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),20 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),21 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),22 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),23 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),24 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),25 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),26 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),27 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),28 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),29 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),30 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),31 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),32 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),33 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),34 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),35 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),36 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),37 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),38 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),39 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),40 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400"),41 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400"),42 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400"),43 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400"),44 GridImage(aspectRatio: 1.0, imageUrl: "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400"),45 GridImage(aspectRatio: 3/4, imageUrl: "https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=400"),46 GridImage(aspectRatio: 9/16, imageUrl: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=400")47 ]48 49 @State private var columnHeights: [CGFloat] = [0, 0, 0]50 @State private var columnItems: [[GridImage]] = [[], [], []]51 52 var body: some View {53 ScrollView {54 HStack(alignment: .top, spacing: 8) {55 ForEach(0..<3) { columnIndex in56 LazyVStack(spacing: 8) {57 ForEach(columnItems[columnIndex]) { item in58 item.view59 }60 }61 }62 }63 .padding(8)64 }65 .onAppear {66 distributeItems()67 }68 }69 70 private func distributeItems() {71 columnHeights = [0, 0, 0]72 columnItems = [[], [], []]73 74 for item in items {75 let shortestColumnIndex = columnHeights.firstIndex(of: columnHeights.min() ?? 0) ?? 076 77 columnItems[shortestColumnIndex].append(item)78 79 let itemHeight = UIScreen.main.bounds.width / 3 / item.aspectRatio80 columnHeights[shortestColumnIndex] += itemHeight + 881 }82 }83}8485struct GridImage: Identifiable {86 let id = UUID()87 let aspectRatio: CGFloat88 let imageUrl: String89 90 var view: some View {91 GeometryReader { geometry in92 AsyncImage(url: URL(string: imageUrl)) { image in93 image94 .resizable()95 .aspectRatio(contentMode: .fill)96 } placeholder: {97 Rectangle()98 .foregroundColor(.gray.opacity(0.3))99 .overlay(100 ProgressView()101 .scaleEffect(0.8)102 )103 }104 .frame(width: geometry.size.width, height: geometry.size.width / aspectRatio)105 .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))106 }107 .aspectRatio(aspectRatio, contentMode: .fit)108 }109}