-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
230: Practice Exercise: Building a Custom PreferenceKey for View Communication
When I first started working with complex SwiftUI layouts, I fell into a trap that I see almost every developer hit: I treated PreferenceKey like it was just a fancy, decoupled version of @Binding. I thought, "If I want a child view to tell the parent something, and the parent to react, I'll just use a PreferenceKey to sync the state."
The Myth: PreferenceKeys are for Two-Way State Syncing
The misconception is that PreferenceKeys are a general-purpose communication channel. You might try to use one to pass a value from a child to a parent, and then immediately have the parent pass a modified version of that value back down to the child via a binding or environment object, all within the same layout cycle.
If you try this, you'll run into the dreaded "Modification of state during view update" warning, or worse, an infinite layout loop that freezes your app. Why? Because PreferenceKeys are designed specifically for the bottom-up flow of information. They aren't meant to "sync" state; they are meant to "report" layout or identity data from the children up to the ancestors after the view hierarchy has been proposed.
The Reality: One-Way Upward Signals
Think of a PreferenceKey as a courier. The child view puts a message in an envelope and sends it up the chain. The parent doesn't "reach down" to grab it; the parent simply listens for when that envelope arrives. The key distinction is that this happens during the layout process. The most common—and correct—use case is when a parent needs to know something about a child's geometry (like its size or position) that the parent couldn't possibly know beforehand.
Let's look at a real scenario: You're building a custom Tab Bar where you want a sliding highlight indicator to move behind the active tab. The parent container needs to know the exact width and X-offset of the selected child tab to animate the highlight bar correctly. The parent can't just "guess" the width because the text in the tabs might change based on localization.
// First, we define the key.
// We use a struct and conform to PreferenceKey.
struct TabWidthKey: PreferenceKey {
// The default value if no child provides one
static var defaultValue: CGFloat = 0
// This is the magic: how to merge multiple values from different children
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
// The child view "reports" its width
struct TabItem: View {
let title: String
var body: some View {
Text(title)
.background(GeometryReader { geometry in
Color.clear.preference(key: TabWidthKey.self, value: geometry.size.width)
})
}
}
// The parent "listens" for the report
struct TabBar: View {
@State private var selectedTabWidth: CGFloat = 0
var body: some View {
HStack {
TabItem(title: "Home")
TabItem(title: "Settings")
}
.onPreferenceChange(TabWidthKey.self) { width in
// Now the parent knows the width and can update the UI
self.selectedTabWidth = width
}
}
}
Notice what happened there. The TabItem didn't try to change a variable in the parent directly. It just declared, "My width is X," and the TabBar opted into listening for that specific key. I prefer this approach because it keeps the TabItem completely agnostic of who its parent is. It just shouts its width into the void, and whoever cares can listen.
A quick tip: Always be careful with what you put in reduce. If you have multiple children sending values for the same key, reduce determines how those values are combined. In my example, I just took the latest value, but if you were building a list of sizes, you'd probably use an array and append the nextValue to the existing collection.
📋 Practical Task
Exercise: Building a Child-Sized Header Highlight
Your task is to build a view where a "Highlight Bar" (a thin colored rectangle) automatically matches the width of a child text element. Since the text can change, the parent must rely on a PreferenceKey to determine the bar's width.
- Create a
PreferenceKeynamedHeaderWidthKeythat stores aCGFloat. - Create a child view called
HeaderTitleViewthat uses aGeometryReaderinside a.background()` modifier to send its own width up through theHeaderWidthKey. - Create a parent view called
CustomHeaderthat contains theHeaderTitleViewand aRectangle()` (the highlight bar) positioned below it. - Use
.onPreferenceChange(HeaderWidthKey.self)in the parent to update a@Statevariable that controls the.frame(width:)of the highlight bar.
Goal: When you change the text in HeaderTitleView, the highlight bar should automatically snap to the new width of the text.
There are no comments for now.