SwiftUI FlowLayout-like View overflowing over other view in VStack

251 Views Asked by At

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.

1

There are 1 best solutions below

0
P5music On BEST ANSWER

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

            }
        }
    }
}