From 0bcb8f849ce149d39f3b18cfff1876b4260c9d26 Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Tue, 19 May 2026 09:53:42 +0300 Subject: [PATCH] Force uniform 1:1 square crop on all gallery cells Replace ZStack-based cell with Color.clear.aspectRatio(1,.fit) + .overlay. The overlay constrains all children (image, video badge, sync dot) to the exact square frame, so scaledToFill cannot inflate the cell height in LazyVGrid. Previously, the ZStack let the filled image push beyond the aspectRatio constraint, causing inconsistent row heights. Co-Authored-By: Kutesir Co-Authored-By: Sentry --- Features/Browse/GalleryView.swift | 46 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/Features/Browse/GalleryView.swift b/Features/Browse/GalleryView.swift index d96cd8b..2e886ec 100644 --- a/Features/Browse/GalleryView.swift +++ b/Features/Browse/GalleryView.swift @@ -260,31 +260,29 @@ struct GalleryCell: View { @State private var taskID = UUID() var body: some View { - ZStack { - cellBackground - if item.mediaType == .video, image != nil { - videoOverlay.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading) + // Color.clear anchors the 1:1 square; .overlay constrains all children to that + // exact frame so scaledToFill never inflates the cell height in LazyVGrid. + Color.clear + .aspectRatio(1, contentMode: .fit) + .overlay { + if let img = image { + Image(uiImage: img) + .resizable() + .scaledToFill() + .transition(.opacity.animation(.easeIn(duration: 0.18))) + } else { + Rectangle() + .fill(AppTheme.surfaceSunken) + .shimmer() + } } - syncDot.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing) - } - .aspectRatio(1, contentMode: .fit) - .clipped() - .id(taskID) - .task(id: taskID) { await loadThumbnail() } - } - - @ViewBuilder - private var cellBackground: some View { - if let img = image { - Image(uiImage: img) - .resizable() - .scaledToFill() - .transition(.opacity.animation(.easeIn(duration: 0.18))) - } else { - Rectangle() - .fill(AppTheme.surfaceSunken) - .shimmer() - } + .overlay(alignment: .bottomLeading) { + if item.mediaType == .video, image != nil { videoOverlay } + } + .overlay(alignment: .bottomTrailing) { syncDot } + .clipped() + .id(taskID) + .task(id: taskID) { await loadThumbnail() } } private var videoOverlay: some View {