Mastering Anchor Preferences in SwiftUI for Dynamic Layouts
SwiftUI's layout system is incredibly flexible, but sometimes you need information about a child view's size or position to influence a parent view's layout. This is where Anchor Preferences shine. They provide a robust, declarative way to communicate layout data up the view hierarchy, enabling complex and dynamic UI constructions.

Understanding SwiftUI's Layout System and Its Limitations
SwiftUI's declarative nature makes UI development intuitive. You describe what your UI should look like, and SwiftUI handles the 'how'. However, this unidirectional data flow for layout information—from parent to child—can sometimes be a limitation. While a parent view dictates its children's positions and sizes (at least in part, based on their preferred sizes), children typically can't directly inform a parent about their actual computed layout metrics.
Consider a scenario where you want to draw an overlay that perfectly matches the frame of one of your child views, but the parent view needs to render this overlay. Or perhaps you want to animate a transition based on the initial and final positions of a view that changes its place in the hierarchy. Without a mechanism to push layout information up, these tasks become challenging, often requiring imperative GeometryReader hacks or complex coordinate space conversions. This is precisely the problem Anchor Preferences solve, providing a declarative and elegant solution.
What Are Anchor Preferences?
Anchor Preferences are a sophisticated mechanism introduced in SwiftUI (iOS 13.0+, macOS 10.15+) that allows child views to communicate their layout metrics (like size, position, or specific anchor points) up the view hierarchy to a parent view. Unlike regular Preference keys which accumulate simple values, AnchorPreference keys deal with Anchor<Value> types. An Anchor<Value> is a proxy for a specific point or frame in a view's coordinate space that can be resolved into a concrete CGRect or CGPoint later, when the layout is finalized.
The power of Anchor Preferences lies in their ability to capture layout information after the child view has been laid out, but before the parent view needs to react to that information. This ensures that the parent receives accurate, post-layout data. You define a custom AnchorKey that specifies how values are combined when multiple child views contribute, and then attach modifiers to your views to publish and consume these preferences.
The Core Components:
PreferenceKey(specificallyAnchorKey): You define a customAnchorKeyconforming toPreferenceKeythat holds anAnchor<CGRect>orAnchor<CGPoint>. It must implementdefaultValueand areduce(value:next:)static method to combine multiple preferences..anchorPreference(key:value:): This view modifier is attached to a child view to publish an anchor or a collection of anchors up the view hierarchy. You provide a key path to the desired anchor (.bounds,.top,.center, etc.)..onPreferenceChange(key:_:): This view modifier is attached to a parent view (or an ancestor) to read the preference value. The closure receives the accumulated preference value when it changes.GeometryReader: Often used in conjunction with.onPreferenceChangeto resolve the into concrete layout metrics within a specific coordinate space. 's or methods are crucial here.
Defining a Custom AnchorKey
The first step is to define a custom PreferenceKey that will carry our anchor information. This key must conform to PreferenceKey where its Value is an Anchor<CGRect> (or Anchor<CGPoint>). The reduce static method is particularly important as it dictates how multiple values of the same key, published by different child views, are combined. For layout information, you typically want to either collect all of them into an array or select one specific anchor.
Let's create a key to track the bounding CGRect of specific views.
Publishing Anchor Preferences from Child Views
Now that we have our PreferenceKey, we can attach the .anchorPreference modifier to any child view from which we want to extract layout information. This modifier takes the PreferenceKey type and a closure that returns the anchor value to publish. You typically use a GeometryProxy inside this closure to access a view's anchor.
In this example, we'll create a list of items and publish their individual frames, keyed by their index.
Consuming and Resolving Anchor Preferences
The final step is to consume these published preferences in an ancestor view. You use the .onPreferenceChange modifier, which provides the accumulated value whenever it changes. Inside the closure, you'll often need a GeometryReader to resolve the Anchor<Value> into concrete CGRects or CGPoints within a specific coordinate space. The GeometryProxy's anchor(_:) method is key here, which takes an Anchor<Value> and returns the resolved Value in the GeometryReader's coordinate space.
Let's extend our ContentView to draw a border around the last selected item using the collected preferences. We'll add a state variable to track the selected item's ID.
Advanced Usage: Animating with Anchor Preferences
Anchor preferences are incredibly powerful for animations, especially when a view moves between different parts of the screen or changes its size. By capturing the 'before' and 'after' anchors, you can precisely animate custom effects.
Consider a scenario where you want to animate a custom transition of a ProgressView from a collapsed state to an expanded state, where its final position is determined by an AnchorPreference. You can use transaction and animation modifiers to smoothly transition between preference updates.
Another common advanced use case involves forEach loops where items might dynamically appear or disappear. You can collect all item anchors, and when an item is removed, its last known anchor can be used to animate a 'fly-off' effect or to adjust the layout of other items smoothly. The reduce function in your PreferenceKey becomes crucial for managing these dynamic sets of anchors.
Common Pitfalls and Best Practices
While powerful, Anchor Preferences can introduce complexity if not used carefully. Here are some common pitfalls and best practices:
- Performance:
onPreferenceChangecan be called frequently during layout passes. Avoid performing heavy computations inside its closure. Only update minimal state or trigger necessary downstream updates. - Infinite Loops: Be cautious when updating state variables directly within
onPreferenceChange. If the state change causes a layout recalculation that in turn changes the preference, you could end up in an infinite loop. It's often safer to use these values for drawing overlays or informing a distinct layout pass that won't immediately feedback into the preference source. - Coordinate Spaces: Always be mindful of coordinate spaces. An
Anchor<CGRect>from a child view is relative to that child's local coordinate space. When you resolve it usingGeometryProxy,geometry[anchor]converts it into theGeometryReader's coordinate space. Ensure you are comparing or drawing in the correct space. reduce(value:next:)Implementation: This is critical. If your key collects an array of anchors, make sure yourreducefunction correctly appends or merges them. If it's a single value, decide whether to take the first, last, or combine them.overlayPreferenceValuevs.onPreferenceChange: For drawing dynamic overlays based on child view layouts,overlayPreferenceValue(or ) is often more idiomatic and performs better than followed by an or modifier. directly consumes the preferences to build a view in the overlay, avoiding a state update cycle.
When to Use Anchor Preferences
Anchor preferences are your go-to solution for specific layout challenges:
- Drawing Arrows/Indicators: When you need to draw an arrow or line from one view to another, or point to a specific element on screen.
- Highlighting/Overlaying: Creating dynamic highlight boxes or overlays around existing UI elements, like in our example.
- Custom Transitions/Animations: Animating views from one arbitrary position to another, leveraging precise start and end frame information.
- Alignment Guides: Implementing complex custom alignment logic where a parent needs to align views based on a child's specific internal point.
- Multi-View Coordination: Scenarios where several views contribute layout information that a common ancestor needs to aggregate and react to.
Compatibility Notes
Anchor Preferences were introduced with the initial release of SwiftUI. Therefore, they are available on:
- iOS 13.0+
- macOS 10.15+
- tvOS 13.0+
- watchOS 6.0+
This broad compatibility means you can confidently use them in most modern SwiftUI projects. Always verify your deployment targets.
Custom AnchorKey Definition
Empowering Dynamic UI with Declarative Layout Metrics
Custom AnchorKey Definition
Defines a custom key to collect `CGRect` anchors from multiple views, identified by an integer key, merging them into a dictionary.
struct MyRectKey: PreferenceKey {
static var defaultValue: [Int: Anchor<CGRect>] = [: ]
static func reduce(value: inout [Int: Anchor<CGRect>], next: () -> [Int: Anchor<CGRect>]) {
value.merge(next()) { (current, new) in new }
}
}Task Hierarchy / Steps
Imagine needing to draw a custom popover with an arrow that precisely points to the center of a button, regardless of the button's position on screen or its size changes after layout.
1. Button publishes its `.bounds` anchor using a custom `PreferenceKey`.
2. Popover's parent view consumes this anchor preference via `overlayPreferenceValue`.
3. Inside `GeometryReader`, the parent resolves the button's anchor into a `CGRect` in its own coordinate space.
4. Parent calculates the precise arrow position and popover frame based on the resolved `CGRect`.
5. Popover and arrow are drawn, dynamically adjusting to button's position.
Visualized execution hierarchy.
Powerful Guarantees
Unidirectional Flow
Solves the limitation where child views typically can't send layout info directly to parents.
Delayed Resolution
Anchors are proxies, resolved into concrete `CGRect` or `CGPoint` only after layout, ensuring accuracy.
Declarative Syntax
Integrates seamlessly with SwiftUI's declarative paradigm, avoiding imperative layout hacks.
Powerful Animations
Enables complex custom transitions and visual effects based on dynamic view positions.
Implementing a Custom Popover Arrow
Imagine needing to draw a custom popover with an arrow that precisely points to the center of a button, regardless of the button's position on screen or its size changes after layout.
struct MyRectKey: PreferenceKey {
static var defaultValue: [Int: Anchor<CGRect>] = [: ]
static func reduce(value: inout [Int: Anchor<CGRect>], next: () -> [Int: Anchor<CGRect>]) {
value.merge(next()) { (current, new) in new }
}
}Interview Perspective: Anchor Preferences
“Explain the core patterns of this concept.”
Defines a custom key to collect `CGRect` anchors from multiple views, identified by an integer key, merging them into a dictionary.
- Explain the problem Anchor Preferences solve in SwiftUI's layout.
- Describe the `PreferenceKey` protocol and the significance of `reduce(value:next:)`.
- How do you publish an anchor? (`.anchorPreference`)
- How do you consume and resolve an anchor? (`.onPreferenceChange`, `.overlayPreferenceValue`, `GeometryReader`, `geometry[anchor]`)
- Discuss common use cases and potential pitfalls (e.g., performance, coordinate spaces).
Defines a custom key to collect `CGRect` anchors from multiple views, identified by an integer key, merging them into a dictionary.
Common Interview Questions
What's the difference between Anchor Preferences and regular Preference Keys?
Regular `PreferenceKey`s pass arbitrary `Equatable` values up the view hierarchy, often used for simple data aggregation like toolbar titles. `AnchorPreference`s specifically deal with `Anchor<Value>` types (like `Anchor<CGRect>` or `Anchor<CGPoint>`), which are proxies to layout geometry. These anchors must be resolved within a `GeometryReader` against a specific coordinate space to get concrete `CGRect` or `CGPoint` values, making them ideal for dynamic layout and drawing.
Can I use Anchor Preferences to move a view dynamically?
Yes, indirectly. Anchor Preferences allow you to *read* the position/size of a view. You can then use this information in a parent view to position *another* view, or to calculate an offset for the original view. For instance, you could read a view's anchor, then use that resolved `CGPoint` to set the `offset` of an overlay or a new view, effectively moving something else relative to the first view's position.
Why do I need `GeometryReader` to resolve an anchor?
An `Anchor<Value>` is just a reference to a point or rectangle within a view's *local* coordinate space. It doesn't have a concrete position on the screen until it's 'resolved' within a global context. `GeometryReader` provides a `GeometryProxy` that represents a specific coordinate space. By calling `geometry[anchor]`, you're telling SwiftUI to convert that local anchor reference into a concrete `CGPoint` or `CGRect` relative to the `GeometryReader`'s frame, making it useful for drawing or layout decisions in that particular coordinate space.
What if multiple child views publish values for the same Anchor Preference key?
This is handled by the `reduce(value:next:)` static method you implement in your `PreferenceKey`. This method is called repeatedly by SwiftUI to combine all the preference values published by views in the hierarchy into a single final value that is then passed to `onPreferenceChange` or `overlayPreferenceValue`. You decide the logic: accumulate into an array, take the last one, average them, etc.