What Is Geometry Reader in Swiftui

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:

  1. You have two geometry identifiers with different meanings.
  2. 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)

demo

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:

  1. Make a @State property to store the height
  2. Set it using an .onAppear { attached to Color.clear
  3. 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:

"Hello world!" above "Height of first text is 20.333333"

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



Leave a reply



Submit