I have a simple grid view where I have cells that show a square image with a couple of lines of text below it.
I'm trying to get the image to fill the square, clipped and not squashed in any way. I have got close, but the image seems to be squashed:
struct CollectionsView: View {
    @StateObject var viewModel: CollectionsViewModel = CollectionsViewModel()
    var body: some View {
        ScrollView {
            let columns = [GridItem(spacing: 16), GridItem(spacing: 16)]
            LazyVGrid(columns: columns) {
                ForEach(viewModel.collections) { collection in
                    Button {
                        print("go to collection")
                    } label: {
                        CollectionsViewCell(collection: collection)
                    }
                    .onAppear {
                        if collection == viewModel.collections.last {
                            Task {
                                try await viewModel.getCollections(nextPage: true)
                            }
                        }
                    }
                }
            }
            .padding(16)
        }
        .navigationTitle("Collections")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    // more button pressed
                } label: {
                    Image(systemName: "ellipsis.circle")
                }
            }
        }
    }
}
struct CollectionsViewCell: View {
    let collection: HashableCollection
    var body: some View {
        VStack(alignment: .leading) {
            AsyncImage(url: URL(string: collection.imageUrlString)) { image in
                image
                    .resizable()
                    .aspectRatio(1, contentMode: .fill)
            } placeholder: {
                Rectangle()
            }
            .background(.secondary)
            .clipped()
            .cornerRadius(11)
            Text(collection.title)
                .lineLimit(1)
                .truncationMode(.tail)
            Text("\(collection.itemCount) item\(collection.itemCount == 1 ? "" : "s")")
                .foregroundColor(.secondary)
        }
    }
}
I have tried using Geometry Reader, but it doesn't behave well inside the ScrollView - is there a better way to achieve this?
// UPDATE //
I have come up with solution, but it seems a bit messy... is there a better way?
struct CollectionsView: View {
    @StateObject var viewModel: CollectionsViewModel = CollectionsViewModel()
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                let spacing: Double = 16
                let columns = [GridItem(spacing: spacing), GridItem(spacing: spacing)]
                LazyVGrid(columns: columns) {
                    ForEach(viewModel.collections) { collection in
                        Button {
                            print("go to collection")
                        } label: {
                            let geometryInfo = GeometryInfo(size: geometry.size, columnCount: Double(columns.count), spacing: spacing)
                            CollectionsViewCell(geometryInfo: geometryInfo, collection: collection)
                        }
                        .onAppear {
                            if collection == viewModel.collections.last {
                                Task {
                                    try await viewModel.getCollections(nextPage: true)
                                }
                            }
                        }
                    }
                }
                .padding(spacing)
            }
            .navigationTitle("Collections")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        // more button pressed
                    } label: {
                        Image(systemName: "ellipsis.circle")
                    }
                }
            }
        }
    }
}
struct CollectionsViewCell: View {
    let geometryInfo: GeometryInfo
    let collection: HashableCollection
    var body: some View {
        VStack(alignment: .leading) {
            AsyncImage(url: URL(string: collection.imageUrlString)) { image in
                image
                    .resizable()
                    .scaledToFill()
            } placeholder: {
                Rectangle()
            }
            .frame(width: getImageSize(), height: getImageSize())
            .background(.secondary)
            .clipped()
            .cornerRadius(11)
            Text(collection.title)
                .lineLimit(1)
                .truncationMode(.tail)
            Text("\(collection.itemCount) item\(collection.itemCount == 1 ? "" : "s")")
                .foregroundColor(.secondary)
        }
    }
    func getImageSize() -> Double {
        (geometryInfo.size.width - ((geometryInfo.columnCount + 1) * geometryInfo.spacing)) / geometryInfo.columnCount
    }
}

 
                        
Just use these modifiers for your
image:result:
The complete example snippet (starting from your code) is: