swiftuihstack

SwiftUI HStack layout from right to left


Trying to layout a stack of "chips" in two side-by-side panels in a mirrored way, fanned out from the middle:

enter image description here The right panel looks correct, the left is messed up. What am I doing wrong?

Here is the code:

import SwiftUI
let w = UIScreen.main.bounds.width

struct ContentView: View {
    var body: some View {
        HStack(spacing:0) {
            VStack(alignment: .trailing) {
                ForEach(1 ..< 5, id: \.self) { row in
                    HStack(spacing: -80) {
                        ForEach(1 ... row, id: \.self) { _ in
                            Circle().fill(.white).stroke(.black, lineWidth: 1).frame(width: 50, height: 50)
                        }
                    }.background(.red)
                }
            }.frame(maxWidth: .infinity, alignment: .trailing)
                .frame(width: w / 2)
                .background(Color.gray)
            VStack(alignment: .leading) {
                ForEach(1 ..< 5, id: \.self) { row in
                    HStack(spacing: -20) {
                        ForEach(1 ... row, id: \.self) { _ in
                            Circle().fill(.white).stroke(.black, lineWidth: 1).frame(width: 50, height: 50)
                        }
                    }
                }
            }.frame(maxWidth: .infinity, alignment: .leading)
                .frame(width: w / 2)
                .background(Color.black)
        }
    }
}

#Preview {
    ContentView()
}

Solution

  • In the first VStack, you could try setting a zIndex on the circles, so that the circles that come first in the row (from left to right) are shown "higher". The spacing should also be -20, like in the second VStack:

    VStack(alignment: .trailing) {
        ForEach(1 ..< 5, id: \.self) { row in
            HStack(spacing: -20) { // 👈 changed
                ForEach(1 ... row, id: \.self) { i in // 👈 changed
                    Circle()
                        .fill(.white)
                        .stroke(.black, lineWidth: 1)
                        .frame(width: 50, height: 50)
                        .zIndex(Double(-i)) // 👈 added
                }
            }
            .background(.red)
        }
    }
    .frame(maxWidth: .infinity, alignment: .trailing)
    .frame(width: w / 2)
    .background(Color.gray)
    

    Screenshot


    Btw, UIScreen.main is deprecated and doesn't work with iPad split screen. You might want to consider using a GeometryReader to measure the screen width instead:

    GeometryReader { geo in
        let w = geo.size.width
        HStack(spacing:0) {
            // content as before
        }
        .frame(maxHeight: .infinity)
    }