SwiftUI Layout works very differently than UIKit; Wherein UIKit we dealt with raw frames, and either added constraints accordingly or have some concrete frame-set to specify the position of views, in SwiftUI, the layouts are defined in form of stacks, spacers and paddings.
This layout system provides us a lot of simple and flexible ways to define our view's position, but sometimes scenarios arise where you need to have specific frames or size set for a view either by setting some concrete value for which SwiftUI have provided .frame modifier. There are also conditions where you need to define the size or position of a view relative to its parent, and for this particular use case, SwiftUI have provided us with GeometryReader.
GeometryReader is an special container view like any other container view such as VStack, ZStack and Group but the special part is, the closure this container has, have an parameter of type GeometryProxy with contains information about this containers size and safeAreaInsets, also it has an function frame(in: CoordinateSpace) -> CGRect which give us frames for an certain CoordinateSpace (more on this in later section of the article).
You can use this information to size/position our view relative to the size/position of it's superview and any view in the hierarchy.
Before you indulging deeper into geometry reader, let's look at an alternative way to fetch frame properties of a view without using geometry readers.
An Alternative
So, what if you want an element to consume like 1/4th or 1/3rd of the screen? Well, you can go UIScreen.main.bounds. The downside is it only works if your app is only portrait since it doesn't adapt to the different orientations.
VStack(spacing: 0) {
Color.orange
.frame(height: UIScreen.main.bounds.height * (1/3)) // 1
Color.red
}
Result:
Code Explanation:
- Here, the height of the orange color is set to the 1/3rd of the screen height. You can see that the screen height is fetched from UIScreen.main.bounds
Basic Implementation
GeometryReader works by wrapping our view inside the view builder closer, it acts as a container.
For accessing the frame properties, you could just wrap the view around a geometry reader and use the geometry proxy in it. The job of geometry proxy is to provide you with the frame in a certain coordinate space (talked in detail in upcoming sections).
Let’s see geometry reader in action by having a couple of colors in a VStack:
GeometryReader { reader in // 1
VStack {
Color.blue
.frame(height: reader.size.height * (1/5)) // 2
.cornerRadius(10)
.padding()
Color.green
.frame(height: reader.size.height * (1/2)) // 3
.cornerRadius(10)
.padding()
}
}
Result:
Code Explanation:
As we discussed earlier, here the view is wrapped around the reader.
Here, color blue was used in VStack (for sake of simplicity) with height that is 1/5th of the whole screen.
Here, green color is made to cover half of the screen.
The key here is to observe that even if the simulator changes the orientation, the view looks as excepted and adapts to it (which would not possible with UIScren.main.bounds).
Coordinate Spaces
There are three coordinate spaces available to use in geometry reader:
Global - Frame relative to the whole screen/superview ie. the whole content view
Local - Frame relative to the immediate parent view
Custom - The custom coordinate space (identifying a view by adding .coordinateSpace(name: ""))
Global and Local Coordinate Space
Let's make a list item with text wrapped around the geometry reader. The text should display the mid x and y value of the global and local frames. Let's also add some bold title just in a VStack in order to add some extra content to the overall view.
VStack(alignment: .leading) {
Text("Title")
.bold()
.font(.title)
.padding()
List {
GeometryReader { reader in
Text("Global : (\(Int(reader.frame(in: .global).midX)), \(Int(reader.frame(in: .global).midY))) Local: (\(Int(reader.frame(in: .local).midX)), \(Int(reader.frame(in: .local).midY)))")
}
}
}
Result:
You can observe how the global Y coordinate changes as you change the scroll position of the item. Moreover, the local coordinates remain the same since that frame is relative to the list cell view.
Custom Coordinate Space
Now that you understood global and local coordinate space, let's take a look at custom coordinate space.
var body: some View {
GeometryReader { reader in
ZStack { // 1
Color.blue
.coordinateSpace(name: "blue") // 2
.cornerRadius(10)
Color.red
.frame(height: reader.frame(in: .named("blue")).height * 1/2) // 3
.cornerRadius(10)
.padding()
}.padding()
}
}
Result:
Code Explanation:
Here, there is a ZStack in which are we are going to put the content
The blue view is given the coordinate space name of "blue" so this can be used later
The height of the red view is calculated such that it is half of the coordinate space "blue" (aka the blue view)
Here, the key is .coordinateSpace(name: "blue"). If you wouldn't have named that coordinated space, the red view wouldn't have been able to get blue view's frame.
Moreover, you can see how using a coordinate space can give use frame of the specific view required.
Congratulations! You deserve to celebrate this moment. Today you learned about geometry reader in SwiftUI and what is its significance, alternative way to get the frame properties using UIScreen bounds and different type of coordinate spaces and its usage. We encourage you to read more related articles like Colors and Gradient in SwiftUI till then, have a great time ahead.
Eat. Sleep. Swift Anytime. Repeat.
Top comments (0)