Is it possible that the blue tags (which are currently truncated) are displayed completely and then it automatically makes a line break?
NavigationLink(destination: GameListView()) {
  VStack(alignment: .leading, spacing: 5){
    // Name der Sammlung:
    Text(collection.name)
      .font(.headline)
    // Optional: Für welche Konsolen bzw. Plattformen:
    HStack(alignment: .top, spacing: 10){
      ForEach(collection.platforms, id: \.self) { platform in
        Text(platform)
          .padding(.all, 5)
          .font(.caption)
          .background(Color.blue)
          .foregroundColor(Color.white)
          .cornerRadius(5)
          .lineLimit(1)
      }
    }
  }
  .padding(.vertical, 10)
}

Also, there should be no line breaks with in the blue tags:

That's how it should look in the end:

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:

And here is full demo code (orientation is supported automatically):
import SwiftUI
struct 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()
    }
}
                        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:

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)
                        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
I had the same problem I've, to solve it I pass the object item to a function which first creates the view for the item, then through the UIHostController I will calculate the next position based on the items width. the items view is then returned by the function.
import SwiftUI
class TestItem: Identifiable {
    
    var id = UUID()
    var str = ""
    init(str: String) {
        self.str = str
    }
    
}
struct AutoWrap: View {
    
    var tests: [TestItem] = [
        TestItem(str:"Ninetendo"),
        TestItem(str:"XBox"),
        TestItem(str:"PlayStation"),
        TestItem(str:"PlayStation 2"),
        TestItem(str:"PlayStation 3"),
        TestItem(str:"random"),
        TestItem(str:"PlayStation 4"),
    ]
    
    
    
    var body: some View {
        
        var curItemPos: CGPoint = CGPoint(x: 0, y: 0)
        var prevItemWidth: CGFloat = 0
        return GeometryReader { proxy in
            ZStack(alignment: .topLeading) {
                ForEach(tests) { t in
                    generateItem(t: t, curPos: &curItemPos, containerProxy: proxy, prevItemWidth: &prevItemWidth)
                }
            }.padding(5)
        }
    }
    
    func generateItem(t: TestItem, curPos: inout CGPoint, containerProxy: GeometryProxy, prevItemWidth: inout CGFloat, hSpacing: CGFloat = 5, vSpacing: CGFloat = 5) -> some View {
        let viewItem = Text(t.str).padding([.leading, .trailing], 15).background(Color.blue).cornerRadius(25)
        let itemWidth = UIHostingController(rootView: viewItem).view.intrinsicContentSize.width
        let itemHeight = UIHostingController(rootView: viewItem).view.intrinsicContentSize.height
        let newPosX = curPos.x + prevItemWidth + hSpacing
        let newPosX2 = newPosX + itemWidth
        if newPosX2 > containerProxy.size.width {
            curPos.x = hSpacing
            curPos.y += itemHeight + vSpacing
        } else {
            curPos.x = newPosX
        }
        prevItemWidth = itemWidth
        return viewItem.offset(x: curPos.x, y: curPos.y)
    }
}
struct AutoWrap_Previews: PreviewProvider {
    static var previews: some View {
        AutoWrap()
    }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With