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
}
}