Custom Binding with SwiftUI
A couple of days ago I was looking at some SwiftUI questions and saw one from a fellow trying to store the value of a picker into some Core Data. I thought I would share my solution here. Two caveats:
This is for SwiftUI 1.0 - the person asking the question was using SwiftUI 2.0 (in beta as of this writing), but I think my solution will still work.
This solution is not something uniquely mine - you will see other people mention it in their posts and writing. I thought I would highlight it specifically.
Here’s the problem: you want to use a component (DatePicker
in this case) which transfers the value selected by the user via a Binding. If you are coming from UIKit thinking, your instinct is to use a delegate. Very understandable but that isn’t going to work for SwiftUI. The way to use the picker is:
private @State var selectedDate = Date()
…
DatePicker(
selection: self.$selectedDate,
displayedComponents: [.date]) {
EmptyView()
}.labelsHidden()
When the user changes any of the wheels of the DatePicker
the value of selectedDate
is immediately changed.
The question asked was: How can the value (selectedDate
) be stored into Core Data? More generically, how do you react to this choice made by the user?
If you wanted the UI to change when the user picked a date, you can set that up easily using an IF
statement and SwiftUI will detect that selectedDate
changed, run the body
code again, execute your IF
statement, and produce a new View
which it with then use to change the screen. That’s pretty straightforward. But what if you want to do something non-UI with the value when its changed?
The solution is to use a custom binding. Let’s keep selectedDate
as the backing store variable so it can be used elsewhere and add a new var
which reacts to the DatePicker
changing its selection in a custom way:
private var handleBinding: Binding<Date> {
Binding(
get: { self.selectedDate },
set: { (newValue) in
self.selectedDate = newValue
// do something with newValue, like call another function
// or store it with Core Data or fetch some remote data})}
and then put this to use:
DatePicker(
selection: self.handleBinding,
displayedComponents: [.date]) {
EmptyView()
}.labelsHidden()
The @State
variable that was used in the binding with the DatePicker
is replaced with the custom binding, handleBinding
. It works exactly the same, except when the DatePicker
’s selected date changes, the handleBinding
set
function is called, setting the @State
variable and then doing whatever else you want it to do.
You can of course, use custom binding with your own components. If you component has a @Binding var
in it, you can pass a custom binding. This will give you more control over the flow of data in your SwiftUI app, just don’t abuse it and write sloppy code. Use it only when you need to and try to keep your code clean and readable.