SwiftUI overlay alignment for horizontal centering

38 views Asked by At

I need view A to be centered on the screen (vertically and horizontally). I need view B to appear 30px below view A, also centered horizontally. But A must remain in the center of the screen, without moving upward when B is added. I can accomplish most of this by using .overlay and GeometryReader:

struct ContentView: View {
        
    var body: some View {
        VStack {
            ZStack {
                Rectangle()
                    .fill(.red)
                    .frame(width: 200, height: 100)
                    .overlay {
                        GeometryReader {geo in
                            Rectangle()
                                .fill(.blue)
                                .frame(width: 100, height: 100)
                                .offset(y: geo.size.height + 30)
                        }
                    }
            }
        }
    }
}

enter image description here

I don't know how to center the blue Rectangle horizontally though. I don't necessarily know its width, although it's specified here for visual purposes. Assuming I don't know the exact frame values for either view, how I can I position B 30px below A while keeping A in the center of the screen? And is there a better view structure for accomplishing this?

2

There are 2 answers

1
Benzy Neez On BEST ANSWER

You could just use a VStack with a hidden copy of the blue rectangle above the red one:

struct ContentView: View {
    
    private var blueRectangle: some View {
        Rectangle()
            .fill(.blue)
            .frame(width: 100, height: 100)
    }
    
    var body: some View {
        VStack(spacing: 30) {
            blueRectangle.hidden()
            Rectangle()
                .fill(.red)
                .frame(width: 200, height: 100)
            blueRectangle
        }
    }
}

Screenshot

Alternatively, if the blue rectangle is not expected to be wider than the red one then here's how you could do it with an overlay:

var body: some View {
    Rectangle()
        .fill(.red)
        .frame(width: 200, height: 100)
        .overlay(alignment: .top) {
            GeometryReader { proxy in
                blueRectangle
                    .frame(maxWidth: proxy.size.width)
                    .fixedSize(horizontal: false, vertical: true)
                    .offset(y: proxy.size.height + 30)
            }
        }
}
2
Ricardo Gellman On

Sometimes it sounds pretty obvious, but the red rectangle is vertically centered using .alignmentGuide(.center), while the blue rectangle is aligned below it by calculating its vertical position relative to the red rectangle's height + 30-pixel offset. Both rectangles inherit horizontal centering within the ZStack, ensuring they remain centered on the screen as below

struct ContentView: View {
    var body: some View {
        VStack {
            ZStack {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 200, height: 100)
                    .alignmentGuide(.center) { _ in
                        0
                    }
                    .overlay {
                        GeometryReader { geo in
                            Rectangle()
                                .fill(Color.blue)
                                .frame(width: 100, height: 100)
                                .alignmentGuide(.center) { _ in
                                    geo.size.height + 30
                                }
                        }
                    }
            }
        }
    }
}