I found on StackOverflow some questions about creating a flexible layout (FlowLayout), like tags are usally displayed, with different buttons' width according to the text, on multiple line if necessary.
The code is something like:
@State var buttonStrings:[String]=[String]()
init()
{
self.buttonStrings=createStrings()
}
private func item(for text: String) -> some View {
Button(action:{doSomething()})
{
Text(text)
}
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(self.buttonStrings, id: \.self) { string in
self.item(for: string)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width)
{
width = 0
height -= d.height
}
let result = width
if string == self.buttonStrings.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if string == self.buttonStrings.last! {
height = 0 // last item
}
return result
})
}
}
}
It works, but when this kind of view is inside a VStack and another view follows, it overflows vertically onto the other view (or it ousts it to the bottom, according to the layout configuration and the presence of Stacks).
How can that be avoided and the flexible view be not overflowing or being too big? In fact the Views inside it are displaced and this lead to the issue.
The correct way to use that code is
struct FlowLayoutLikeView:View
{
var geometry:GeometryProxy
@State var buttonStrings:[String]=[String]()
init(geometry:GeometryProxy,....)
{
self.geometry=geometry
...
...
}
var body: some View {
self.generateContent(in: geometry)
}
private func item(for text: String) -> some View {
Button(action:{doSomething()})
{
Text(text)
}
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(self.buttonStrings, id: \.self) { string in
self.item(for: string)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width)
{
width = 0
height -= d.height
}
let result = width
if string == self.buttonStrings.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if string == self.buttonStrings.last! {
height = 0 // last item
}
return result
})
}
}
}
The containing View has to be like
var body: some View {
VStack //this does the trick, do not remove
{
GeometryReader { geometry in
VStack //this also does the trick, do not remove
{
FlowLayoutLikeView(geometry)
AnotherView() //this will be fine, it will be placed below the FlowLayoutLikeView, no overlapping, overflowing or ousting
}
}
}
}