TL;DR: Use
GeometryReader
,onGeometryChange
,visualEffect
, orcontainerRelativeFrame
to dynamically retrieve and respond to view dimensions in SwiftUI. Each method caters to specific use cases and levels of customization.
Background
Dynamically retrieving the dimensions of a view is a common requirement in SwiftUI, especially in scenarios where layouts need to respond to size changes. This article introduces several methods to obtain view dimensions and their applicable use cases.
Using GeometryReader
GeometryReader
is a highly adaptable method compatible with all SwiftUI versions. It is often used with overlay
or background
to avoid interfering with the main view’s layout.
Example Code
blueRectangle
.background(
GeometryReader { proxy in
Color.clear // Create a transparent view matching the main view's dimensions
.task(id: proxy.size) {
size = proxy.size // Retrieve and monitor size changes
}
}
)
Features
- Encapsulates size retrieval logic within
background
, ensuring no impact on the main view’s layout. - Uses
task(id:)
to fetch dimensions immediately when the view loads and update them whenproxy.size
changes.
Using onGeometryChange
Starting from iOS 16, onGeometryChange
provides a more concise way to monitor size changes.
Example Code
struct SizeDemo: View {
@State var size: CGSize?
var body: some View {
Rectangle()
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: {
size = $0
}
}
}
iOS 18+ New Feature
Supports retrieving both old and new size values, making it easier to handle size change logic.
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: { old, new in
size = new // Retrieve the new size
print("Old size: \(old), New size: \(new)")
}
Using visualEffect
For dynamically applying visual effects based on view dimensions (e.g., offset
, scaleEffect
), visualEffect
(iOS 17+) provides a more direct approach.
Example Code
struct SizeDemo: View {
var body: some View {
Rectangle()
.foregroundStyle(.red)
.visualEffect { effect, proxy in
effect
.offset(y: proxy.size.height / 3) // Offset the view by 1/3 of its height
}
}
}
Effect
Using containerRelativeFrame
containerRelativeFrame
(iOS 17+) combines the functionality of GeometryReader
and frame
, allowing you to retrieve the size of the view’s parent container (e.g., window, NavigationStack
, or ScrollView
) and apply it as a constraint.
Example Code
The following code sets a rectangle’s width to half and height to a quarter of the window’s size:
struct TransformsDemo: View {
var body: some View {
Rectangle()
.containerRelativeFrame([.horizontal, .vertical]) { length, axis in
if axis == .vertical {
return length / 4
} else {
return length / 2
}
}
}
}
containerRelativeFrame
searches upward for the nearest container, which in this case is the window.
Effect
Container Adaptability
containerRelativeFrame
automatically selects the appropriate container. For instance, when placed within a NavigationStack
, the dimensions are calculated relative to the NavigationStack
.
struct TransformsDemo: View {
var body: some View {
NavigationStack {
Rectangle()
.containerRelativeFrame([.horizontal, .vertical]) { length, axis in
if axis == .vertical {
return length / 4
} else {
return length / 2
}
}
}
.frame(width: 300, height: 300) // Set the size of the NavigationStack
.border(.red, width: 4)
}
}
Effect
Application in ScrollView
containerRelativeFrame
can also be used to dynamically adjust the size of child views within a scroll view. For example, setting the width of child views to one-third of the scroll view’s width:
struct ScrollViewDemo: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(0..<10) { _ in
Rectangle()
.fill(.purple)
.aspectRatio(3 / 2, contentMode: .fit)
.containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 0)
}
}
}
}
}
Effect
Method Comparison
Method | Use Case | Features |
---|---|---|
GeometryReader | Applicable to all SwiftUI versions | Highly flexible but requires more custom code |
onGeometryChange | Simplifies size change monitoring (iOS 16+) | Clear semantics, supports old/new size (iOS 18+) |
visualEffect | For dynamic rendering effects (iOS 17+) | Simple and efficient, directly uses GeometryProxy |
containerRelativeFrame | Relative size calculation for containers (iOS 17+) | Automatically adapts to container context |