NEW: Join my free 100 Days of SwiftUI challenge today! >>

Creating custom bindings in SwiftUI

Paul Hudson    @twostraws   

Because of the way SwiftUI sends binding updates to property wrappers, assigning property observers used with property wrappers won’t work, which means this kind of code won’t print anything even as the blur radius changes:

struct ContentView: View {
    @State private var blurAmount: CGFloat = 0 {
        didSet {
            print("New value is \(blurAmount)")
        }
    }

    var body: some View {
        VStack {
            Text("Hello, World!")
                .blur(radius: blurAmount)

            Slider(value: $blurAmount, in: 0...20)
        }
    }
}

To fix this we need to create a custom binding – we need to use the Binding struct directly, which allows us to provide our own code to run when the value is read or written.

In our code, we want a Binding to return the value of blurAmount when it’s read, but when it’s written we want to change the value of blurAmount and also print that new value so we can see that it changed. Regardless of whether we’re reading or writing, we’re talking about something that reads our blurAmount property, and Swift doesn’t allow us to create properties that read other properties because the property we’re trying to read might not have been created yet.

So, putting all that together we need to create a custom Binding struct that acts as a passthrough around blurAmount, but when we’re setting the value we also want to print a message. It’s also a requirement that we don’t store it as a property of our view, because reading one property from another isn’t allowed.

As a result, we need to put this code into the body property of our view, like this:

struct ContentView: View {
    @State private var blurAmount: CGFloat = 0

    var body: some View {
        let blur = Binding<CGFloat>(
            get: {
                self.blurAmount
            },
            set: {
                self.blurAmount = $0
                print("New value is \(self.blurAmount)")
            }
        )

        return VStack {
            Text("Hello, World!")
                .blur(radius: blurAmount)

            Slider(value: blur, in: 0...20)
        }
    }
}

Before we get into the binding itself, notice how some other things have stayed the same: we still use @State private var to declare the blurAmount property, and we still use blur(radius: blurRadius) as the modifier for our text view.

One thing that changed is the way we declare the binding in the slider: rather than using $blurAmount we can just use blur. This is because using the dollar sign is what gets us the two-way binding from some state, but now that we’ve created the binding directly we no longer need it.

OK, now let’s look at the binding itself. As you should be able to figure out from the way we used it, the basic initializer for a Binding looks like this:

init(get: @escaping () -> Value, set: @escaping (Value) -> Void)

You can find that by press Cmd+Shift+O and looking up “Binding” in the generated interface for SwiftUI. Breaking that down, it’s telling us that the initializer takes two closures: a getter that takes no parameters and returns a value, and a setter that takes a value and returns nothing. Binding uses generics, so that Value is really a placeholder for whatever we’re storing inside – a CGFloat in the case of our blur binding. Both the get and set closures are marked as @escaping, meaning that the Binding struct stores them for use later on.

What all this means is that you can do whatever you want inside these closures: you can call methods, run an algorithm to figure out the correct value to use, or even just use random values – it doesn’t matter, as long as you return a value from get. So, if you want to make sure you update UserDefaults every time a value is changed, the set closure of a Binding is perfect.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5