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)