Ahoy, captain! Give me the Anchor!
iOS SwiftUI Anchor GeometryReader Estimated reading time: 8 minutesBuilding a complex UI may require data exchange between different parts of the view hierarchy. Often we may require an exact position or some coordinate point during various animation processes or even just a point.
Back in UIKit
, everything is quite simple - we may ask UIView
to convert values (such as frame (CGRect
) or point (CGPoint
)) from one coordinate system to another and send data using various mechanisms (for example delegate or closure).
check Converting Between View Coordinate Systems part of this official documentation
If we think about SwiftUI
- things are a bit different: we can’t use any of these mechanisms. So what we have instead? The answer is Anchor
.
Anchor
Anchor
is just a struct, that can wrap some value - value that u want to manage and pass between view’s. The official description of this - An opaque value derived from an anchor source and a particular view.
The declaration of this:
Inside we also see a nested struct that represents Source
- something that generates the value that we want to pass and manipulate later. Source
can be used within GeometryProxy
object (using subscript mechanism).
GeometryProxy
can be obtained fromGeometryReader
.
Source is managed using PreferenceKey
via special viewModifier - anchorPreference
:
I wrote separate article about PreferenceKey, check it out for more.
Removing generic parameters and constraints for simplification we can see that this view modifier requires from us a few things:
key
to which value will be temporarily storedsource
- where to find the value andtransform block
- how to transform and represent obtained value.
I would like to mention that in theory, we can pass any Value
in the Anchor
struct - as u already saw, there are no requirements to it. So we may want to pass our type not related to geometry, but this probably a bad coding style.
In case if u would like to use if for passing CGRect
or CGPoint
, Apple has prepared for us predefined set of possible options and operations similar to one existing in UIKit
:
Example
The best way to understand something is by trying it.
As for me, I prefer to have a bit of understanding of an idea before actual coding, that’s why I put a description of how it should be used above.
We may create a simple view with a few nested views and pass geometry between them. Code for the view will be very simple:
I added extra
.padding
on few buttons just to make changes more obvious - u will see it later.
To test Anchor
firstly, as was mention above, we should create a PreferenceKey
- value that can hold data for us:
Here, we declared TextBoundsKey
that will be used to manage CGRect
(Anchor<CGRect>
) - frame of each button.
Next - we can already use the preference key using the modifier described above - anchorPreference
. To make things a bit better, we may create a View
extension for this. Adding any input parameters that can be used for the transform block is also an option.
So let’s define extension with additional input - flag that can help us to decide whenever we should capture value. Also - pay attenstion to a value
- .bounds
used - one of predefined options from Anchor<CGRec>
:
Now, we may add logic to the view that allows us to capture coordinates of the Button
only if we press it. To do so we need a @State
variable for holding the index of the button and our captureBounds
function:
Here u can see that whenever we press a button, the index of a button is stored and captureBounds
receive a true
flag, that enables Anchor
capturing inside the transform block.
Now we may add action on this preference key change. Unfortunately we can’t use existing function for this onPreferenceChange(perform:)
because its has Equitable
requirements. To solve this, Apple has added overlayPreferenceValue(::)
.
Thus we capture CGRect
, let’s draw a rectangle using transferred value - rect around a selected button. But before adding code for this, I would like to remember how to obtain transferred value - we should use GeometryReader
and available subscript from GeometryProxy
:
Usage within our example may be next:
Here the magic is in this code - geometryProxy[value]
.
This is quite a simple example, and we use everything within one View
. An example is good only for initial testing, but in real life we never gonna have such a case - instead, a more complex view tree will be used.
So let’s make this example a bit more realistic and so complex. To do so we may extract Button
to separate View
and add some additional parameter that can be moved within a CGRect
- the color of the rectangle that is drawn around a selected button.
Let’s start by defining our data type for transferring CGRect
and Color
:
Than - modified version of preferenseKey extension that now use our data type AnchorValue
:
Extracted ButtonView
:
And finally, our view that uses anchorPreference:
Result:
This example demonstrates a bit more complex viewTree and data.
Consclusion
Using Anchor
, we easily can manage the view’s, its geometry and transfer data between viewTree components. This is a very useful technique that helps us build a more complex layout.
Share on: