SwiftUI – Using GeometryReader Without Modifying The View Size

Answer to the question in the title:

  • It is possible to wrap the GeometryReader in an .overlay() or .background(). Doing so will mitigate the layout changing effect of GeometryReader. The view will be laid out as normal, the GeometryReader will expand to the full size of the view and emit the geometry into its content builder closure.
  • It’s also possible to set the frame of the GeometryReader to stop its eagerness in expanding.

For example, this example renders a blue rectangle, and a “Hello world” text inside at 3/4th the height of the rectangle (instead of the rectangle filling up all available space) by wrapping the GeometryReader in an overlay:

struct MyView : View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(height: 150)
            .overlay(GeometryReader { geo in
                Text("Hello world").padding(.top, geo.size.height * 3 / 4)
            })
        Spacer()
    }
}

Another example to achieve the same effect by setting the frame on the GeometryReader:

struct MyView : View {
    var body: some View {
        GeometryReader { geo in
            Rectangle().fill(Color.blue)
            Text("Hello world").padding(.top, geo.size.height * 3 / 4)
        }
        .frame(height: 150)

        Spacer()
    }
}

Render with "Hello world" on 3/4th the height of a blue rectangle.

However, there are caveats / not very obvious behaviors

1

View modifiers apply to anything up to the point that they are applied, and not to anything after. An overlay / background that is added after .edgesIgnoringSafeArea(.all) will respect the safe area (not participate in ignoring the safe area).

This code renders “Hello world” inside the safe area, while the blue rectangle ignores the safe area:

struct MyView : View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(height: 150)
            .edgesIgnoringSafeArea(.all)
            .overlay(VStack {
                        Text("Hello world")
                        Spacer()
            })

        Spacer()
    }
}

Render of "Hello world" inside the safe area.

2

Applying .edgesIgnoringSafeArea(.all) to the background makes GeometryReader ignore the SafeArea:

struct MyView : View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(height: 150)
            .overlay(GeometryReader { geo in
                VStack {
                        Text("Hello world")
                            // No effect, safe area is set to be ignored.
                            .padding(.top, geo.safeAreaInsets.top)
                        Spacer()
                }
            })
            .edgesIgnoringSafeArea(.all)

        Spacer()
    }
}

Render of "Hello world" ignoring the safe area.

It is possible to compose many layouts by adding multiple overlays / backgrounds.

3

A measured geometry will be available to the content of the GeometryReader. Not to parent or sibling views; even if the values are extracted into a State or ObservableObject. SwiftUI will emit a runtime warning if that happens:

struct MyView : View {
    @State private var safeAreaInsets = EdgeInsets()

    var body: some View {
        Text("Hello world")
            .edgesIgnoringSafeArea(.all)
            .background(GeometryReader(content: set(geometry:)))
            .padding(.top, safeAreaInsets.top)
        Spacer()
    }

    private func set(geometry: GeometryProxy) -> some View {
        self.safeAreaInsets = geometry.safeAreaInsets
        return Color.blue
    }
}

Runtime warning "Modifying state during view update, this will cause undefined behavior."

Leave a Comment

tech