I'm somewhat new to swift and am struggling with this seemingly simple problem. I want three columns in my grid UNLESS the content of the grid item doesn't fit within the set width of the column. Specifically, I have filter buttons and some of which have long names. I want to send the TagView of "Breakfast" to the next line and move each one along so that they all fit and the text doesn't have to wrap. Not sure how to do this though.
I've tried adjusting the minimum and maximum values within the grid items and adjusted how many GridItems are in the columns. However, this hasn't worked unfortunately. Any advice would be much appreciated.
Here is my code:
var filterBar: some View {
VStack {
filterBarTitle
if filtersOpened {
let columns = [GridItem(.adaptive(minimum: 85), spacing:0)]
LazyVGrid(columns: columns) {
ForEach(allRecipeTags) { recipeTag in
TagView(tag: recipeTag, selected: selectedTags.contains(recipeTag), canToggle: true, onTap: {
if selectedTags.contains(recipeTag) {
print("removing \(recipeTag)")
selectedTags.removeAll(where: { $0 == recipeTag })
} else {
print("adding \(recipeTag)")
selectedTags.append(recipeTag)
}
})
.border(.red)
}
}
.border(.blue)
}
}
.padding([.top, .bottom])
.border(width: 1, edges: [.bottom], color: Color("24292F").opacity(0.3))
.padding(.horizontal)
.padding([.bottom], filtersOpened ? 8 : 0)
}
Here is a simple implementation of a custom Layout
, which hopefully suits your purpose:
struct ContentView: View {
let tags = ["Appetizers","Baking","Breakfast","Dessert","Dinner","Fish","Lunch","Main","Quick","Side","Snack","Spicy","Vegan","Vegetarian"]
var body: some View {
FlowLayout {
ForEach(tags, id: \.self) { tag in
HStack {
Text(tag)
.fixedSize()
.font(.headline)
Circle()
}
.padding(10)
.background(
Capsule().stroke(.red)
)
.padding(5)
}
}
}
}
struct FlowLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var totalHeight: CGFloat = 0
var totalWidth: CGFloat = 0
var lineWidth: CGFloat = 0
var lineHeight: CGFloat = 0
for size in sizes {
if lineWidth + size.width > proposal.width ?? 0 {
totalHeight += lineHeight
lineWidth = size.width
lineHeight = size.height
} else {
lineWidth += size.width
lineHeight = max(lineHeight, size.height)
}
totalWidth = max(totalWidth, lineWidth)
}
totalHeight += lineHeight
return .init(width: totalWidth, height: totalHeight)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var lineX = bounds.minX
var lineY = bounds.minY
var lineHeight: CGFloat = 0
for index in subviews.indices {
if lineX + sizes[index].width > (proposal.width ?? 0) {
lineY += lineHeight
lineHeight = 0
lineX = bounds.minX
}
subviews[index].place(
at: .init(
x: lineX + sizes[index].width / 2,
y: lineY + sizes[index].height / 2
),
anchor: .center,
proposal: ProposedViewSize(sizes[index])
)
lineHeight = max(lineHeight, sizes[index].height)
lineX += sizes[index].width
}
}
}