SwiftUI HStack with Wrap SwiftUI HStack with Wrap ios ios

SwiftUI HStack with Wrap


Here is some approach of how this could be done using alignmentGuide(s). It is simplified to avoid many code post, but hope it is useful.

Update: There is also updated & improved variant of below solution in my answer for SwiftUI HStack with wrap and dynamic height

This is the result:

swiftui wrapped layout

And here is full demo code (orientation is supported automatically):

import SwiftUIstruct TestWrappedLayout: View {    @State var platforms = ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"]    var body: some View {        GeometryReader { geometry in            self.generateContent(in: geometry)        }    }    private func generateContent(in g: GeometryProxy) -> some View {        var width = CGFloat.zero        var height = CGFloat.zero        return ZStack(alignment: .topLeading) {            ForEach(self.platforms, id: \.self) { platform in                self.item(for: platform)                    .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 platform == self.platforms.last! {                            width = 0 //last item                        } else {                            width -= d.width                        }                        return result                    })                    .alignmentGuide(.top, computeValue: {d in                        let result = height                        if platform == self.platforms.last! {                            height = 0 // last item                        }                        return result                    })            }        }    }    func item(for text: String) -> some View {        Text(text)            .padding(.all, 5)            .font(.body)            .background(Color.blue)            .foregroundColor(Color.white)            .cornerRadius(5)    }}struct TestWrappedLayout_Previews: PreviewProvider {    static var previews: some View {        TestWrappedLayout()    }}


I've had ago at creating what you need.

Ive used HStack's in a VStack.

You pass in a geometryProxy which is used for determining the maximum row width.I went with passing this in so it would be usable within a scrollView

I wrapped the SwiftUI Views in a UIHostingController to get a size for each child.

I then loop through the views adding them to the row until it reaches the maximum width, in which case I start adding to a new row.

This is just the init and final stage combining and outputting the rows in the VStack

struct WrappedHStack<Content: View>: View {        private let content: [Content]    private let spacing: CGFloat = 8    private let geometry: GeometryProxy        init(geometry: GeometryProxy, content: [Content]) {        self.content = content        self.geometry = geometry    }        var body: some View {        let rowBuilder = RowBuilder(spacing: spacing,                                    containerWidth: geometry.size.width)                let rowViews = rowBuilder.generateRows(views: content)        let finalView = ForEach(rowViews.indices) { rowViews[$0] }                VStack(alignment: .center, spacing: 8) {            finalView        }.frame(width: geometry.size.width)    }}extension WrappedHStack {        init<Data, ID: Hashable>(geometry: GeometryProxy, @ViewBuilder content: () -> ForEach<Data, ID, Content>) {        let views = content()        self.geometry = geometry        self.content = views.data.map(views.content)    }    init(geometry: GeometryProxy, content: () -> [Content]) {        self.geometry = geometry        self.content = content()    }}

The magic happens in here

extension WrappedHStack {    struct RowBuilder {                private var spacing: CGFloat        private var containerWidth: CGFloat                init(spacing: CGFloat, containerWidth: CGFloat) {            self.spacing = spacing            self.containerWidth = containerWidth        }                func generateRows<Content: View>(views: [Content]) -> [AnyView] {                        var rows = [AnyView]()                        var currentRowViews = [AnyView]()            var currentRowWidth: CGFloat = 0                        for (view) in views {                let viewWidth = view.getSize().width                                if currentRowWidth + viewWidth > containerWidth {                    rows.append(createRow(for: currentRowViews))                    currentRowViews = []                    currentRowWidth = 0                }                currentRowViews.append(view.erasedToAnyView())                currentRowWidth += viewWidth + spacing            }            rows.append(createRow(for: currentRowViews))            return rows        }                private func createRow(for views: [AnyView]) -> AnyView {            HStack(alignment: .center, spacing: spacing) {                ForEach(views.indices) { views[$0] }            }            .erasedToAnyView()        }    }}

and here's extensions I used

extension View {    func erasedToAnyView() -> AnyView {        AnyView(self)    }        func getSize() -> CGSize {        UIHostingController(rootView: self).view.intrinsicContentSize    }}

You can see the full code with some examples here:https://gist.github.com/kanesbetas/63e719cb96e644d31bf027194bf4ccdb


For me, none of the answers worked. Either because I had different types of elements or because elements around were not being positioned correctly. Therefore, I ended up implementing my own WrappingHStack which can be used in a very similar way to HStack. You can find it at GitHub: WrappingHStack.

Here is an example:

enter image description here

Code:

WrappingHStack {    Text("WrappingHStack")        .padding()        .font(.title)        .border(Color.black)        Text("can handle different element types")        Image(systemName: "scribble")        .font(.title)        .frame(width: 200, height: 20)        .background(Color.purple)        Text("and loop")        .bold()        WrappingHStack(1...20, id:\.self) {        Text("Item: \($0)")            .padding(3)            .background(Rectangle().stroke())    }.frame(minWidth: 250)}.padding().border(Color.black)