What is Geometry Reader in SwiftUI?
UPDATE
Since I posted the answer, I have also written an article on how GeometryReader works. Check it out for a more detailed explanation: https://swiftui-lab.com/geometryreader-to-the-rescue/
GeometryReader is a view that gives you access to the size and position of it's parent. For example:
struct MyView: View {
var body: some View {
GeometryReader { geometry in
// Here goes your view content,
// and you can use the geometry variable
// which contains geometry.size of the parent
// You also have function to get the bounds
// of the parent: geometry.frame(in: .global)
}
}
}
I usually combine it with .background() to obtain some other view's bounds. For example, The Text view is hard to predict how large it would be in advance. When I need that information, I use this trick:
First I have defined a view called GeometryGetter:
struct GeometryGetter: View {
@Binding var rect: CGRect
var body: some View {
return GeometryReader { geometry in
self.makeView(geometry: geometry)
}
}
func makeView(geometry: GeometryProxy) -> some View {
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}
return Rectangle().fill(Color.clear)
}
}
Then, to get the bounds of a Text view (or any other view):
struct MyView: View {
@State private var rect: CGRect = CGRect()
var body: some View {
Text("some text").background(GeometryGetter($rect))
// You can then use rect in other places of your view:
Rectangle().frame(width: 100, height: rect.height)
}
}
For some use cases, I posted some answers to other questions that use GeometryReader. Check them out:
Move textfields to avoid being hidden by the keyboard: https://stackoverflow.com/a/56721268/7786555
How to make view the size of another view in SwiftUI:
https://stackoverflow.com/a/56661706/7786555
Note
In GeometryGetter, I added a DispatchQueue.main.async {} to set the rect. In some cases it could lead to runtime warning otherwise: Modifying state during view update.
binding the GeometryReader in swiftui
To make this easier to understand the solution, here is a minimal reproducible example of the problem:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
geometry(geometry: geometry)
}
}
}
struct geometry: View {
@Binding var geometry: GeometryProxy
var body: some View {
Circle()
.foregroundColor(.blue)
.opacity(0.5)
.blur(radius: 5)
.frame(width: geometry.size.width * 0.04, height: geometry.size.width * 0.04)
}
}
There are 2 main issues:
- You have two
geometry
identifiers with different meanings. - You cannot pass a
Binding
into the initializer.
#1
You have geometry
which is a GeometryProxy
and another geometry
which is a View
. The inner geometry
(GeometryProxy
) is preferred since it is in the most local scope. This means that you can't access the geometry
view.
You can solve this by using a capital letter on the view, which you should do by convention anyway (to avoid problems like this):
struct Geometry: View {
/* ... */
}
#2
Now that you have solved the first problem, you will now get the error:
Cannot convert value of type 'GeometryProxy' to expected argument type 'Binding'
Solve this by changing the line:
@Binding var geometry: GeometryProxy
To:
let geometry: GeometryProxy
SwiftUI measuring the height of a view
The showing size is the dimension of the full view that is the container of the inner view.
You need to use another GeometryReader to get the inner dimension of a second ZStack.
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo.size.height)")
}
GeometryReader { innterGeo in //<Here
ZStack {
Rectangle()
.foregroundColor(.red)
.coordinateSpace(name: "Redbox")
Text("Height of red box is \(innterGeo.frame(in: .named("Redbox")).height)")
}
}
}
}
}
}
if you need to use this in any places then you can use this approch.
First, create one struct and wrapped your content with GeometryReader.
struct GeometryContentSize<Content: View>: View {
public var content: (CGSize) -> Content
var body: some View {
GeometryReader { geo in
content(geo.size)
}
}
}
usage:
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack {
ZStack {
Rectangle()
.foregroundColor(.blue)
Text("Heigt of full screen is \(geo.size.height)")
}
GeometryContentSize { (size) in //<--- Here
ZStack {
Rectangle()
.foregroundColor(.red)
Text("Height of red box is \(size.height)")
}
}
}
}
}
}
How to Align GeometryReader in SwiftUI?
It looks like you placed .frame
modifier in wrong place (placement of modifier is important)
Here is modified code (assuming I understood your intention)
var body: some View {
GeometryReader { geo in
Text("\(randomRoll)")
.font(.title)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity) // << before background !!
.background(
Rectangle()
.foregroundColor(Color(hue: min(1, geo.frame(in: .global).minY/CGFloat(randomNumber) ), saturation: 1, brightness: 1))
.cornerRadius(10))
}
.frame(height: 150)
}
SwiftUI - How to get GeometryReader size/height from different View?
You can:
- Make a
@State
property to store the height - Set it using an
.onAppear {
attached toColor.clear
- Replace
???
with\(textHeight)
struct ContentView: View {
@State var textHeight = CGFloat(0) /// 1.
var body: some View {
VStack {
Text("Hello world!")
.background(
GeometryReader { proxy in
Color.clear
.onAppear { /// 2.
textHeight = proxy.size.height
}
}
)
/// 3.
Text("Height of first text is \(textHeight)")
}
}
}
Result:
GeometryReader behavior in SwiftUI
Uh, I am surprised to see that. Actually in Xcode 11.4 the default alignment for the GeometryReader
is .center
, however in Xcode 12 beta 3, the default alignment looks top-leading
.
I have not seen any related information yet. However I can suggest wrapping Text
into VStack
or HStack
and providing frame from proxy
. It will provide better layout organization, in my opinion of course.
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { proxy in
VStack {
Text("First Stack")
.foregroundColor(.black)
.font(.largeTitle)
}
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
}
.background(Color.red.opacity(0.4))
Text("Second Stack").background(Color.blue)
}
.background(Color.yellow.opacity(0.5))
} }
Related Topics
Nsuserdefaults Not Working on Xcode Beta With Watch Os2
Binary Operator * Cannot Be Applied to Operands of Type Int and Double
Check Password String Strength Criteria in Swift
Choosing Coredata Entities from Form Picker
Swift: How to Use Preprocessor Flags (Like '#If Debug') to Implement API Keys
What Is _: in Swift Telling Me
Detect When a Tab Bar Item Is Pressed
Using a Type Variable in a Generic
Call a Method from a String in Swift
Get Integer Value from String in Swift
What Does a Module Mean in Swift
Failing Cast in Swift from Any? to Protocol
Wrong Specialized Generic Function Gets Called in Swift 3 from an Indirect Call