swiftuigeometryoverflowflowlayout

SwiftUI FlowLayout-like View overflowing over other view in VStack


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.


Solution

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