SwiftUI : stack overlapping elements horizontally?

Viewed 1368

I would like to stack overlapping elements horizontally, like this:

enter image description here

Here is what I tried:

struct StackedElementsView: View {
    let colors: [Color] = [.red, .blue, .purple]
    
    var body: some View {
        HStack {
            ZStack {
                ForEach(0..<colors.count) { i in
                    ZStack(alignment: .leading) {
                        colors[i]
                            .clipShape(Circle())
                            .frame(width: 44, height: 44)
                    }
                    .offset(x: CGFloat(i) * 25)
                }
            }
            .padding(.leading, 24)
            Color.purple
                .frame(width: 100, height: 44)
            Spacer()
        }
        .padding(.vertical, 8)
    }
}

struct StackedElementsView_Previews: PreviewProvider {
    static var previews: some View {
        StackedElementsView()
    }
}

I have an issue with this, the purple rectangle overlaps the circles and I don't know why.

Thank you for your help

2 Answers

Here would be a more intuitive solution. You could instead use negative spacing and have only HStacks:

enter image description here

struct StackedElementsView: View {
    let colors: [Color] = [.red, .blue, .purple]
    
    var body: some View {
        HStack {
            HStack(spacing: -25) {
                ForEach(0..<colors.count) { i in
                    colors[i]
                        .clipShape(Circle())
                        .frame(width: 44, height: 44)
                }
            }
            
            Color.purple
                .frame(width: 100, height: 44)
            Spacer()
        }
        .padding(.leading, 24)
        .padding(.vertical, 8)
    }
}

.offset just changes where the view draws, but doesn't change the frame which is why the rectangle draws further left than you expect.

One way to fix this is to add appropriate leading padding to the rectangle:

Color.purple
    .frame(width: 100, height: 44)
    .padding(.leading, CGFloat(colors.count - 1) * 25)

Alternate solution

Instead of using .offset, use a Color.clear view in an HStack to provide the spacing needed:

ForEach(0..<colors.count) { i in
    ZStack(alignment: .leading) {
        HStack(spacing: 0) {
            Color.clear
                .frame(width: CGFloat(i) * 25, height: 44)
            colors[i]
                .clipShape(Circle())
                .frame(width: 44, height: 44)
        }
    }
}
Related