DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Preference Keys & Anchor System Internals

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())
    }
}
Enter fullscreen mode Exit fullscreen mode

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
                )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

Child writes:

.anchorPreference(
    key: BoundsAnchorKey.self,
    value: .bounds
) { $0 }
Enter fullscreen mode Exit fullscreen mode

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

🆚 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:

  1. SwiftUI performs layout
  2. Children emit preferences
  3. Values bubble upward
  4. reduce merges values
  5. Parents observe changes
  6. 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)