SwiftUI normally enforces one-way data flow:
parent → child
But real apps often need the opposite:
- child reports its size to a parent
- scroll position flows upward
- geometry affects headers, toolbars, or overlays
- multiple children contribute values to a container
That’s where PreferenceKeys and Anchors come in.
They are one of the least understood but most powerful internal systems in SwiftUI.
This post explains:
- what PreferenceKeys really are
- how SwiftUI propagates them
- how Anchors differ from GeometryReader
- when to use each
- performance implications
- common mistakes
🧠 What Is a PreferenceKey?
A PreferenceKey is SwiftUI’s reverse data flow mechanism.
It allows:
Child View
↓
Preference Value
↓
Parent View
This breaks the normal top-down rule intentionally.
🧱 Basic PreferenceKey Structure
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
Key parts:
-
defaultValue→ initial state -
reduce→ how multiple children combine values
SwiftUI calls reduce during layout passes.
📐 Writing a Preference from a Child
struct ChildView: View {
var body: some View {
GeometryReader { geo in
Color.clear
.preference(
key: HeightPreferenceKey.self,
value: geo.size.height
)
}
}
}
Important:
- preferences are written during layout
- values may change multiple times per render
- this is not a one-time event
📥 Reading Preferences in a Parent
struct ParentView: View {
@State private var height: CGFloat = 0
var body: some View {
VStack {
ChildView()
}
.onPreferenceChange(HeightPreferenceKey.self) { value in
height = value
}
}
}
This is how parents:
- react to child geometry
- adjust layouts dynamically
- build scroll-driven UI
⚓ What Are Anchors?
Anchors are deferred geometry references.
Instead of passing numbers, you pass a location token.
struct BoundsAnchorKey: PreferenceKey {
static var defaultValue: Anchor<CGRect>? = nil
static func reduce(
value: inout Anchor<CGRect>?,
nextValue: () -> Anchor<CGRect>?
) {
value = nextValue()
}
}
Child writes:
.anchorPreference(
key: BoundsAnchorKey.self,
value: .bounds
) { $0 }
Parent resolves:
.backgroundPreferenceValue(BoundsAnchorKey.self) { anchor in
GeometryReader { geo in
if let anchor {
let rect = geo[anchor]
Color.red
.frame(width: rect.width, height: rect.height)
}
}
}
🆚 Preference vs GeometryReader
| Use Case | Best Tool |
|---|---|
| Parent needs child size | PreferenceKey |
| Child needs its own size | GeometryReader |
| Global overlays | Anchors |
| Scroll effects | Preferences |
| Headers reacting to content | Preferences |
| One-off measurement | GeometryReader |
Rule of thumb
📌 Use GeometryReader locally, Preferences globally.
🔄 How SwiftUI Propagates Preferences
Internally:
- SwiftUI performs layout
- Children emit preferences
- Values bubble upward
- reduce merges values
- Parents observe changes
- Views may invalidate again
This means:
- preference changes can trigger new layout passes
- heavy logic here affects performance
⚠️ Performance Considerations
Avoid:
- expensive calculations in reduce
- writing preferences inside lists without need
- preferences that change every frame
- chaining multiple preference layers
Best practices:
- keep values small (numbers, anchors)
- debounce updates if needed
- prefer anchors over raw geometry for overlays
🧩 Real-World Use Cases
Preferences power:
- collapsible headers
- sticky section headers
- scroll position tracking
- dynamic toolbars
- custom navigation bars
- overlay alignment
- matched layout effects
Many “magic” SwiftUI effects are built on this system.
❌ Common Mistakes
- using Preferences instead of state
- treating them as one-time values
- assuming layout happens once
- doing async work inside preference updates
- stacking GeometryReader + Preferences unnecessarily
Preferences are layout-time signals, not business logic.
🧠 Mental Model
Think of PreferenceKeys as:
layout-time signals flowing upward
Not data storage.
Not state management.
Not events.
They exist only to inform layout decisions.
🚀 Final Thoughts
PreferenceKeys and Anchors are:
- powerful
- subtle
- easy to misuse
- essential for advanced SwiftUI UI
Once you understand them, you unlock:
- clean scroll effects
- adaptive headers
- overlay systems
- layout-driven UI without hacks
Top comments (0)