Getting view size in SwiftUI without GeometryReader
We can avoid GeometryReader now! New modifier supports iOS 16+.
Published: Nov. 26, 2024 Sponsored App StoreRecently I needed to know size of SwiftUI container to layout horizontal carousel. The reason was to have one items always at least a tiny bit visible so users can reliably find the rest by scrolling. I started with GeometryReader and quickly got frustrated and started looking for another solution…
There are many problems with GeometryReader and its behavior, which is why I am trying to avoid it whenever I can.
Anyway… I started looking for alternative sort of expecting to find nothing or perhaps something for iOS 18, but I need to support iOS 16+. However I did discover modifier onGeometryChanged
.
This is actually new with iOS 18 (Xcode 16) but it has been backported all the way to iOS 16. It will tell you when the frame or size of your view changes. Which means you can use this in @State
and update child views accordingly.
The new modifier also makes it easy to create layout loops, so be careful. If I wanted to modify size of the view that has its geometry tracked, that would again invoke the geometry changed and so on.
The onGeometryChanged
approach also seems to work well for “sized to content bottom sheets”. Below is super simple example but it should show all the relevant parts.
You need property to hold the height and then these modifiers:
SheetContentView()
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: {
self.contentHeight = $0.height
// Alternatively you can get the `width` here
}
.presentationDetents([.height(contentHeight)])
Here is entire code you can copy and run:
import SwiftUI
struct ContentView: View {
@State private var contentHeight: CGFloat = 0
@State private var showsSheet = false
@State private var fontSize: CGFloat = 50
var body: some View {
VStack(spacing: 20) {
Text("onGeometryChange example")
.font(.largeTitle)
.multilineTextAlignment(.center)
Button {
fontSize = CGFloat.random(in: 30...80)
showsSheet = true
} label: {
Text("Show sheet")
}
.buttonStyle(.bordered)
}
.padding()
.sheet(isPresented: $showsSheet) {
VStack {
Text("As you can see this sheet is dynamically sized to fit this content.")
.fixedSize(horizontal: false, vertical: true)
.padding()
.font(.system(size: fontSize))
}
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: {
self.contentHeight = $0.height
// Alternatively you can get the `width` here
}
.presentationDetents([.height(contentHeight)])
}
}
}