How to retrieve the frame of a subview in parent view: Leveraging GeometryReader and PreferenceKey

When developing using SwiftUI, there are situations where we need to get the frame of a subview in its ancestor view. For instance, I am currently working on a feature where upon long-pressing a button, the user can move their finger to a specific view in order to cancel a certain process, similar to voice input in WeChat.

At this moment, it is essential to obtain the precise frame of a particular view to verify if the user’s finger is positioned on it.

GeometryReader

To obtain the frame of a view, we can utilize the GeometryReader in SwiftUI. By wrapping a view with a GeometryReader, we can access its precise position and frame infomation.

By incorporating a GeometryReader, we gain the ability to accurately determine the dimensions and location of a specific view.

Look at the following code

1
2
3
4
5
6
7
8
9
struct SomeView: View {
var body: some View {
VStack {
Rectangle()
Rectangle()
Rectangle()
}
}
}

There are three ractangles. If we need to know the x, y, width, height of the middle one. We can put this rectangle inside a GeometryReader, like this:

1
2
3
4
5
6
7
8
9
10
11
struct SomeView: View {
var body: some View {
VStack {
Rectangle()
GeometryReader { geometry in
Rectangle()
}
Rectangle()
}
}
}

Then we can display the size on the rectangle using overlay.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SomeView: View {
var body: some View {
VStack {
Rectangle()
GeometryReader { geometry in
Rectangle()
.overlay(
Text("\(geometry.size.width) \(geometry.size.height)")
.foregroundColor(.white)
)
}
Rectangle()
}
}
}

Also the x, y position:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SomeView: View {
var body: some View {
VStack {
Rectangle()
GeometryReader { geometry in
Rectangle()
.overlay(
Text("\(geometry.frame(in: .global).minX) \(geometry.frame(in: .global).minY)")
.foregroundColor(.white)
)
}
Rectangle()
}
}
}

We have got the exact postion and frame of the middle rectangle.

But the data can only be used in the rectangle view. What if we want to use them in parent view?

We need use the PreferenceKey to pass the infomation from inner to outside.

PreferenceKey

To use a preference key, we need to define a struct first.

1
2
3
struct GeometryPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
}

PreferenceKey is a protocol. To confirm this protocol, we need a defaultValue static property. We can designate the type of defaultValue by our demand. Here, I let it be CGRect and give it a default value .zero.

Then, we must implement another method called reduce.

1
2
3
4
5
6
7
struct GeometryPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero

static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}

This reduce method looks like the common reduce method in Swift standard library. It can be used to handle a initial value and a new value. Combine them by some means. Here, we just replace the old value with the new one. The first parameter value is an inout parameter.

Then we can put the value into the preference by the custom key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct SomeView: View {
@State private var rectangleRect: CGRect = .zero

var body: some View {
VStack {
Rectangle()
GeometryReader { geometry in
Rectangle()
.preference(key: GeometryPreferenceKey.self, value: geometry.frame(in: .global))
}
Rectangle()
}
.onPreferenceChange(GeometryPreferenceKey.self) { value in
print(value)
rectangleRect = value
}
}
}

Give the rectangle a view modifier called preference, specify the key to GeometryPreferenceKey.self we just defined. Put the value we need into the value parameter.

Then use .onPreferenceChange on the outside VStack, we can get the value here.

We can print the value, or store it in a @State property.

So we have got the data we need, we can use it to implement a lot of features.

Conclusion

GeometryReader and PreferenceKey are incredibly useful features in SwiftUI. They provide powerful tools to address a wide range of challenges. It is essential to have a good understanding of these concepts as they can greatly enhance your SwiftUI development skills.