Article source address :swiftui-lab.com/geometryrea…

Author: Javier

Translation: Liaoworking

For the most part, SwiftUI does itMagical layoutFeatures. Sometimes, however, we need to do more with the layout of our custom views. Right now we have several tools. The first thing we need to explore isGeometryReader

What does the parent view want?

When we create custom views, we generally don’t have to worry about the layout or size of the adjacent views. If you want to create a square. As long as you use a Rectangle, you will draw a square with the size and position your parent wants.

In the example below, we have a VStack with a frame of 150×100. The top part is Text, and the rest of the space is MyRectangle(). The images are filled with blue colors:

struct ContentView : View { var body: some View { VStack { Text("Hello There!" ) MyRectangle() }.frame(width: 150, height: 100).border(Color.black) } } struct MyRectangle: View { var body: some View { Rectangle().fill(Color.blue) } }Copy the code

As you can see, MyRectangle(), instead of setting size, has only one task, which is to draw a rectangle. Leave it to SwiftUI to manage the size and location of the child views desired by the parent. In this case, the Vstack is the parent view.

If you want to know more about how a parent view determines the position and size of its children. I highly recommend you to check out 2019WWDC Session 237Building Custom Views With SwiftUI

The parent view automatically finds the appropriate size and location for the child view. But if you want to draw a custom rectangle, it’s half the size of the parent view. A view located 5 pixels to the right of the parent view. It’s not that complicated, and you can use an GeometryReader as a solution.

What does the subview do?

Here’s what Apple’s documentation says about GeometryReader:

A container view that defines its content as a function of its own size and coordinate space. A container view that defines its contents in terms of its size and coordinate space.Copy the code

That’s a pretty detailed explanation.

So what does that mean? An GeometryReader is simply another view. Is it a surprise? Almost everything in SwiftUI is a View. In the following example, the GeometryReader lets you define its content. But it’s different from other views. You can get some information that you can’t get in other views.

It says you want to draw a view that’s half the size of the parent view. A view located 5 pixels to the right of the parent view. Now that we have the GeometryReader, this is easy

struct ContentView : View { var body: some View { VStack { Text("Hello There!" ) MyRectangle() }.frame(width: 150, height: 100).border(Color.black) } } struct MyRectangle: View { var body: some View { GeometryReader { geometry in Rectangle() .path(in: CGRect(x: geometry.size.width + 5, y: 0, width: Geometry. Width / 2.0, height: geometry. Height / 2.0). Fill (color.blue)}}}Copy the code

GeometryProxy

In the example above, Geometry in the closure is a variable of the GeometryProxy class. You can learn more about this topic by Inspecting the View Tree.

In the GeometryProxy class there are two computed properties, a method, and a subscript value.

public var size: CGSize { get }
public var safeAreaInsets: EdgeInsets { get }
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
public subscript<T>(anchor: Anchor<T>) -> T where T : Equatable { get }
Copy the code

The size property is the size suggested by the parent view

GeometryProxy also exposed safeAreaInsets to us.

The frame method exposes the size and location of the parent view’s suggested region. We can use.local,.global,.named() to get different coordinate Spaces. .named() gets a named coordinate space. We can use this name to get other view coordinate Spaces. There is a specific technique for Inspecting the View Tree in this article

Finally, we can obtain an anchor point by subscripting. This is a cool feature of GeometryReader. However, it is also quite tedious. I will explain it in the second part of Inspecting the View Tree. You’ll have an understanding after you read it. You can obtain the size and x,y of any child view in the view tree.

Absorb the Geometry of another View

GeometryReader is already quite powerful, but it can be even more powerful when combined with modifier for **.background()** or **.overlay()**.

In all the tutorials I’ve seen, background is used in the following form: Text(“hello”).background(color.red) color. red is a Color parameter that sets the background Color to red. Its function is to fill the area suggested by the parent view with red. Its parent is the background. And the background changed the Text. Therefore, it is recommended that color. red fill in the same area as Text(“Hello”). Isn’t it beautiful?

The.overlay modifier works the same way, except instead of drawing a background, it draws a foreground.

We already know that we can give it to any view. The Color() method also has the.background() method. Let’s combine the GeometryReader and draw an example of a rectangle that specifies a different radius for each Angle to demonstrate how to use them.

The concrete implementation is as follows:

struct ContentView : View { var body: some View { HStack { Text("SwiftUI") .foregroundColor(.black).font(.title).padding(15) .background(RoundedCorners(color:  .green, tr: 30, bl: 30)) Text("Lab") .foregroundColor(.black).font(.title).padding(15) .background(RoundedCorners(color: .blue, tl: 30, br: 30)) }.padding(20).border(Color.gray).shadow(radius: 3) } } struct RoundedCorners: View { var color: Color =.black var tl: CGFloat = 0.0 var tr: CGFloat = 0.0 var bl: CGFloat = 0.0 var br: CGFloat = 0.0 var body: some View { GeometryReader { geometry in Path { path in let w = geometry.size.width let h = geometry.size.height // We make sure the redius does not exceed the bounds dimensions let tr = min(min(self.tr, h/2), w/2) let tl = min(min(self.tl, h/2), w/2) let bl = min(min(self.bl, h/2), w/2) let br = min(min(self.br, h/2), W /2) path.move(to: CGPoint(x: w/ 2.0, y: 0)) path.addline (to: CGPoint(x: w-tr, y: 0)) path.addarc (center: CGPoint(x: w/ 2.0, y: 0)) path.addarc (center: CGPoint(x: w/ 2.0, y: 0) w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) path.addLine(to: CGPoint(x: w, y: h - br)) path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) path.addLine(to: CGPoint(x: bl, y: h)) path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) path.addLine(to: CGPoint(x: 0, y: tl)) path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) } .fill(self.color) } } }Copy the code

In addition, we set the opacity of the custom Overlay to 0.5 on Text. The code is as follows:

Text("SwiftUI") .foregroundColor(.black).font(.title).padding(15) .overlay(RoundedCorners(color: .green, tr: 30, bl: 30) opacity (0.5)), Text (" Lab "). ForegroundColor. (black). The font. (the title). The padding (15). The overlay (RoundedCorners (color: .blue, tl: 30, br: 30).opacity(0.5))Copy the code

The chicken and egg question

When you start using GeometryReader, you will discover the so-called chicken and egg problem. Because the GeometryReader needs to provide space available to the child attempts, it first needs to occupy as much space as possible. However, subclasses may set a small space, so the GeometryReader should remain as large as possible. Once the child tries to determine the space it needs, you may be forced to shrink the GeometryReader. The child attempts to react to the new size calculated by the GeometryReader. A cycle is created.

So when it comes to whether the child is trying to depend on the size of the parent’s attempt or the parent is trying to depend on the size of the child’s attempt. Perhaps GeometryReader is not the right fit to solve your layout problems. From there, you can look at my next article Preferences and Anchor Preferences.

conclusion

The GeometryReader we learned today lets our custom views know the size and location they need. We also learned about accessing the geometry of other views. This is just the first official article on SwiftUI’s layout tools, but we’ll take a closer look at view’s hierarchy of numbers and how its children try to pass data up. Point I see