Exploring PreferenceKey in SwiftUI
Advanced SwiftUI, SwiftUI Advanced Swift, PreferenceKey, Swift, SwiftUI, XCodeIntroduction
In SwiftUI, data generally flows down from parent to child views, which keeps the UI reactive and straightforward. However, there are cases where we need data to flow upwards, or even be shared across sibling views. This is where PreferenceKey
comes in—a powerful tool for passing data back up to ancestors in the view hierarchy.
What is a PreferenceKey?
A PreferenceKey
is a protocol in SwiftUI that allows us to define a custom preference key, enabling child views to pass data to ancestor views. This is particularly useful when the usual binding
or @Environment
properties can’t easily solve the problem.
Common Use Cases:
- Passing size or geometry view’s data from child to parent views.
- Managing scroll offsets or navigation changes across views.
- Sharing layout information like heights, widths, or other measurements.
Creating a Custom PreferenceKey
To create a PreferenceKey
, you need to define a struct that conforms to the PreferenceKey protocol
. This involves defining a type and a method to combine multiple values of that type.
Here’s a basic example of a custom PreferenceKey
to track a view’s width
:
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
SwiftdefaultValue
: This sets initial value of the preference. In this case, it’s 0 since we are tracking widths.reduce(value:nextValue:)
: This function is used to combine multiple preference values. Here, we’re using max to store the largest width value.
Using the PreferenceKey in Views
Now that we have a PreferenceKey
, we can use it in any SwiftUI view. Suppose we want to track the width of a Text
view and pass that width up to a parent view.
struct ChildView: View {
var body: some View {
Text("Hello, SwiftUI!")
.background(
GeometryReader { geometry in
Color.clear
.preference(key: WidthPreferenceKey.self, value: geometry.size.width)
}
)
}
}
Here, GeometryReader
lets us access the size of the Text
view, and .preference(key:value:)
sets that width as the value for our WidthPreferenceKey
.
Reading the Preference in a Parent View
In the parent view, we can read this width value using .onPreferenceChange
.
struct ParentView: View {
@State private var textWidth: CGFloat = 0
var body: some View {
VStack {
ChildView()
.onPreferenceChange(WidthPreferenceKey.self) { width in
textWidth = width
}
Text("Child View Width: \(textWidth)")
}
}
}
With .onPreferenceChange
, the ParentView
updates textWidth
whenever ChildView
changes its width, displaying the updated width value in real time.
Advanced Example: Building a Custom Tab View
Using PreferenceKey
can enhance complex UIs like a custom tab view. Suppose you want each tab to notify the parent of its selection.
1. Define the PreferenceKey:
struct TabPreferenceKey: PreferenceKey {
static var defaultValue: String = ""
static func reduce(value: inout String, nextValue: () -> String) {
value = nextValue()
}
}
2. Set the Preference in Each Tab:
struct TabView: View {
let label: String
@State private var tabPressed: Bool = false
var body: some View {
Text(label)
.onTapGesture {
tabPressed.toggle()
}
.background(Group {
if self.tabPressed {
Color.clear
.preference(key: TabPreferenceKey.self, value: label)
self.tabPressed.toggle()
}
})
}
}
3. Read the Preference in the Parent:
struct ContentView: View {
@State private var selectedTab: String = ""
var body: some View {
VStack {
HStack {
TabView(label: "Home")
TabView(label: "Settings")
}
.onPreferenceChange(TabPreferenceKey.self) { tab in
selectedTab = tab
}
Text("Selected Tab: \(selectedTab)")
}
}
}
This setup tracks which tab is selected based on the label, and selectedTab
in ContentView
will update accordingly.
Conclusion
PreferenceKey
is a versatile and essential tool in SwiftUI, especially for creating layouts and components that require data to move upwards. By mastering this technique, you can enhance your SwiftUI applications with more flexible and sophisticated UIs.