I don't know if that's possible within a LazyGrid, but here is a possible workaround:
You could simply put the last item inside a VStack and align it centered, whenever the number of items in your data array is uneven.
I have implemented a demo for you:
Simple:
import SwiftUI
//MARK: - Content
struct ContentView: View {
//Your data
let data = Array(0...4)
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
//Same spacing both for items inside grid and between grid and stack
let rowSpacing: CGFloat = 32
//If number of items is odd, remove the last one from grid and add to stack
var gridData: [Int] { data.count%2 == 1 ? data.dropLast() : data }
var stackData: Int? { data.count%2 == 1 ? data.last : nil }
var body: some View {
ScrollView {
VStack(spacing: rowSpacing) {
LazyVGrid(columns: columns, spacing: rowSpacing) {
ForEach(gridData, id: \.self) { i in
ItemView(i: i)
}
}
if let data = stackData {
VStack {
ItemView(i: data)
}
}
}
}
}
}
//MARK: - Item
struct ItemView: View {
let i: Int
var body: some View {
Rectangle()
.frame(width: 160, height: 240)
.foregroundColor(Color.green)
.overlay(Text(String(i)).foregroundColor(.white))
}
}
Alternative, reusable:
//MARK: - Data
struct SampleData: Identifiable {
let id: Int
var text: String
}
//MARK: - View
struct ContentView: View {
//Your data
let data = [SampleData(id: 0, text: "A"), SampleData(id: 1, text: "B"), SampleData(id: 2, text: "C")]
let columns = [
GridItem(.fixed(160)),
GridItem(.fixed(160))
]
var body: some View {
ScrollView {
CenteredLazyVGrid(data, columns: columns, spacing: 32) { i in
ItemView(i: i.id)
}
}
}
}
//MARK: - Item
struct ItemView: View {
let i: Int
var body: some View {
Rectangle()
.frame(width: 160, height: 240)
.foregroundColor(Color.green)
.overlay(Text(String(i)).foregroundColor(.white))
}
}
//MARK: - Centered Grid View
struct CenteredLazyVGrid<Data, Content>: View where Data: RandomAccessCollection, Content: View, /*Data: Hashable, */Data.Element: Identifiable {
private var data: Data
//private var id: KeyPath<Data.Element, ID>
private var columns: [GridItem]
private var alignment: HorizontalAlignment = .center
private var spacing: CGFloat? = nil
private var pinnedViews: PinnedScrollableViews = []
private var content: (Data.Element) -> Content
init(_ data: Data, /*id: KeyPath<Data.Element, ID>, */columns: [GridItem], alignment: HorizontalAlignment = .center, spacing: CGFloat?
= nil, pinnedViews: PinnedScrollableViews = .init(), content: @escaping (Data.Element) -> Content) {
self.data = data
//self.id = id
self.columns = columns
self.alignment = alignment
self.spacing = spacing
self.pinnedViews = pinnedViews
self.content = content
}
private var gridData: [Data.Element] { data.count%2 == 1 ? data.dropLast() : data as! [Data.Element] }
private var stackData: Data.Element? { data.count%2 == 1 ? data.last : nil }
var body: some View {
VStack(spacing: spacing) {
LazyVGrid(columns: columns, alignment: alignment, spacing: spacing, pinnedViews: pinnedViews) {
ForEach(gridData/*, id: id*/) { i in
content(i)
}
}
if let data = stackData {
VStack {
content(data)
}
}
}
}
}