When do you use GeometryReader, and how do safe areas work in SwiftUI?

8.5.2 · L2 · Interview

Predict first

How big are a Color, a width-limited shape and a GeometryReader when offered 200 × 200?

import SwiftUI

func size<V: View>(_ v: sending V, _ w: CGFloat = 400, _ h: CGFloat = 400) -> CGSize {
    UIHostingController(rootView: v).sizeThatFits(in: CGSize(width: w, height: h))
}
print(size(Color.red, 200, 200))
print(size(Rectangle().frame(width: 30), 200, 200))
print(size(GeometryReader { _ in Color.red }, 200, 100))
  • (0, 0), (30, 0), (0, 0)
  • (200, 200), (30, 200), (200, 100)
  • (200, 200), (30, 30), (200, 200)

Mental model. GeometryReader is a tape measure that insists on taking the whole room before it will tell you the size.

GeometryReader gives its content a GeometryProxy with the size and safe-area insets of the space it was offered, and lets you read frames in named coordinate spaces. It is a view that takes all the space offered to it (it behaves like a flexible container), which is the usual source of surprises: dropping one inside a stack suddenly makes it expand. Use it sparingly, when you really need a measurement for layout (a size proportional to the container, a parallax effect). For common needs there are better tools: .frame(maxWidth: .infinity), containerRelativeFrame, ViewThatFits, Layout, alignment guides, or onGeometryChange (newer SDKs) to read a size without altering layout. Never use it just to read the screen size; read the container instead, since the view may not be full screen.

Safe areas: SwiftUI places content inside the safe area by default, so it avoids the notch, home indicator and bars. Background views (Color, Image backgrounds) extend under them when you call .ignoresSafeArea(), optionally for specific edges or regions. .safeAreaInset(edge:) adds your own content (a bottom bar) and shrinks the safe area for what is inside, and .safeAreaPadding adds extra inset. The keyboard is a safe-area region too, which is why scroll views and forms move out of its way, and why .ignoresSafeArea(.keyboard) turns that off. Remember that a view not backed by a background does not need to ignore the safe area; ignore it for the background, and let the content stay inside.

A Color and a GeometryReader take all they are offered, and a frame with only a width still takes the offered height.

Common traps

  • Wrapping everything in GeometryReader. It expands to fill the space and breaks the intrinsic sizing of its parents.
  • Ignoring the safe area for content, not just backgrounds. Text and controls then slide under the notch or home indicator.

They might follow up with

  • What is a better first choice than GeometryReader? Flexible frames, ViewThatFits, a custom Layout, or containerRelativeFrame.
  • How do you add a bottom toolbar that content respects? .safeAreaInset(edge: .bottom).

Related questions

  • 8.5.1 How does SwiftUI’s layout negotiation work, and why do frame and padding order change the result?
  • 8.5.3 How does the Layout protocol work, and when is a custom layout the right tool?
  • 8.8.2 What are the coordinate spaces in SwiftUI, and how do you read a view’s frame?

← How does SwiftUI’s layout negotiation work, and why do frame and…   How does the Layout protocol work, and when is a custom layout the… →