GeometryReader role

In the SwiftUI layout, the parent view and Child View are loosely linked: A Child View cannot obtain the parent View’s size. If the size of the child View needs to be determined by the size of the parent View, this will not work. Let’s say I want a Rectangle that is half the size of the Parent View

struct ContentView: View {
    
    var body: some View {
        VStack{
            Rectangle()
                .frame(width: vstack.width / 2, height: vstack.height / 2)
        }
        .frame(width: 300, height: 300)
        .background(Color.yellow)
    }
}
Copy the code

To implement the above requirements, GeometryReader is needed.

What is GeometryReader

GeometryReader itself is a view, but it differs from the other views in that it exposes an object that can be used to obtain the size of the Parent View, and to Rectangle it. (For Rectangle, the parent View)

var body: some View {
    VStack{
        Rectangle()
            .frame(width: 150, height: 150)
    }
    .frame(width: 300, height: 300)
    .background(Color.yellow)
}
         
Copy the code

In the above code, we replace VStack with GeomertyReader

struct ContentView: View {
    
    var body: some View {
        GeometryReader{ proxy in
            Rectangle()
                .frame(width: 150, height: 150)
        }
        .frame(width: 300, height: 300)
        .background(Color.yellow)
    }
}
Copy the code

Comparing the two codes, the only difference is that GeometryReader exposes the proxy object (the proxy type is GeometryProxy, which doesn’t have much impact on the understanding of this article). Here, FOR those who are not proficient in SWIFT, I will elaborate:

  1. VStackGeometryReaderOf two structuresinitEach method takes a closure function as an argument.
  2. GeometryReadertheinitThe closure function itself takes a proxy object as an argument.

Then, from the exposed proxy object, we can get the size of the GeometryReader view.

struct ContentView: View {
    
    var body: some View {
        GeometryReader{ proxy in
            Rectangle()
                .frame(width: proxy.size.width / 2, height: proxy.size.height / 2)   // This is the point
        }
        .frame(width: 300, height: 300)
        .background(Color.yellow)
    }
}
Copy the code

In the code above, proxy.size is used to obtain the size of GeometryReader.

As an extension, we can put GeometryReader into the VStack

struct ContentView: View {
    
    var body: some View {
        VStack {
            GeometryReader{ proxy in
                Rectangle()
                    .frame(width: proxy.size.width / 2, height: proxy.size.height / 2)
            }
            
        }.frame(width: 300, height: 300)
        .background(Color.yellow)
    }
}
Copy the code

Compare this code to the original code, just introduce GeometryReader between VStack and Rectangle, and then use the size provided by proxy object to get the size of VStack. Rectangle can be set to half the size of VStack.

There’s one detail that’s missing: If you look at the code carefully, the Rectangle size obtained from the proxy should be the size of the GeometryReader, but since the size of the GeometryReader is the same as the size of the VStack, it is the same as the size of the VStack.

The size of the parent View can be obtained through the proxy of GeometryReader. In addition to getting the size of the parent view, the child View can also get its own coordinates within the parent View area, i.e., “where am I in dad’s place?” Look at the following code:

struct ContentView: View {
    
    var body: some View {
        VStack {
            GeometryReader{ proxy in
                Text("I'm in (x: \(proxy.frame(in: .local).minX), y: \(proxy.frame(in: .local).minY))")
                    .font(.title2)
            }
        }
        .frame(height: 200)
        .background(Color.yellow)
    }
}
Copy the code

The running results are as follows:

The proxy provides the frame(in:) method. From the value returned by this method, the Rectangle can know its coordinates in the VStack. (Again, the actual parent view is GeometryReader, but the GeometryReader has the same size and position as the VStack.)

The VStack region is in yellow, and the coordinate system is set at the top left corner. You can see that the Text is in the top left corner of the yellow region, and the starting coordinate is (0, 0). This validates the return value of frame(in:).

Note that frame(in:) has one argument, and we passed.local above. Frame (in:) can actually pass three different arguments:

  1. frame(in: .local)As shown above, the child View can get the position of the parent View area.
  2. frame(in: .global)Using this parameter returns the position of the Child View throughout the screen.
  3. frame(in: .named())Returns the position in the custom coordinate system.

One and two make sense, but what does three mean? Think about it this way: using.local and.global you can get the location in the parent view and the entire screen, but what if you want the child view to get the location in the “grandfather view”? At this point, use frame(in:.named()). The specific functions are as follows:

  1. Any in the Child ViewThe immediateUsed in ancestors.coordinateSpace(name: "custom")Custom coordinates.
  2. Child Viewproxy.frame(in :.named("custom"))I can get my position in that frame.

The specific code is as follows

struct ContentView: View {
    
    var body: some View {
        HStack{
            VStack {
                VStack{
                    GeometryReader{proxy in
                        Text("I'm in (x: \(Int(proxy.frame(in: .named("custom")).minX)), y: \(Int(proxy.frame(in: .named("custom")).minY)))")
                    }
                }.frame(width: 200, height: 200)
                .background(Color.yellow)
            }
            .frame(width: 300, height: 300)
            .background(Color.blue)
            .coordinateSpace(name: "custom")  // Customize a coordinate system}}}Copy the code

When running, you can see that the Text coordinate is (50,50), which is the coordinate system relative to the blue area. The blue area is HStack and is the grandfather view of the Text.

Frame (in:.named()) must be a direct ancestor of the child View. The following code

struct ContentView: View {
    
    var body: some View {
        HStack{
            
            VStack{
                
            }.frame(width: 150, height: 150)
            .background(Color.blue)
            .coordinateSpace(name: "custom")
            
            VStack{
                GeometryReader{proxy in
                    Text("I'm in (x: \(Int(proxy.frame(in: .global).minX)), y: \(Int(proxy.frame(in: .named("custom")).minY)))")
                }
            }.frame(width: 200, height: 200)
            .background(Color.yellow)
            
        }
    }
}
Copy the code

To summarize:

  1. useGeometryReaderexposedproxy.width && proxy.heightGet the size of the parent View.
  2. useGeometryReaderexposedproxy.frame(in:)Method to get the location of the Child View.