Search code examples
swiftuiswiftui-viewswiftui-zstack

SwiftUI - How to overlay or ZStack a View so that it doesn't affect the position of the underlying View?


I want to place an image either on top of, or between lines, depending on the position variable:

struct ContentView: View {
   @State var position = 5

   var body: some View {
        VStack(spacing: 20){
            ForEach(1...15, id: \.self){i in
                ZStack{
                    if i%2 != 0{
                        Rectangle()
                            .frame(height: 4)
                            .foregroundColor(.white)
                    }
                    if i == position{
                        Circle()
                            .frame(height: 30)
                            .foregroundColor(.white)
                    }
                }
            }
        }
    }
}

This is the result:

Screenshot

If i is odd the line (Rectangle) is drawn.

If i == position, the Circle is drawn: it is on top of the line, or if no line was drawn, it is between the other lines.

My problem is that the lines don’t remain fixed when I change the value of position because the Circle takes up space and pushes the lines away from it.

The lines above and below the circle get pushed away when the Circle is between two lines which causes the lines to move back and forth as the Circle is either between or on top of lines.

How would I go about fixing this?


Solution

  • There is two issues here: non-constant height of row (because row with circle and w/o circle have different heights) and conditional layout (absent rectangles gives different layout).

    Here is a possible solution. Tested with Xcode 13.4 / iOS 15.5

    demodemo1

    struct ContentView: View {
       @State var position = 4
    
       var body: some View {
            VStack(spacing: 20){
                ForEach(1...15, id: \.self){i in
                    ZStack {
                        Rectangle()
                            .frame(height: 4)
                            .foregroundColor(i%2 == 0 ? .clear : .white)  // << here !!
                        if i == position{
                            Circle()
                                .foregroundColor(.white)
                                .frame(height: 30)
                        }
                    }.frame(height: 4)    // << here !!
                }
            }
        }
    }