SwiftUI binding: A very simple trick

Binding is a fundamental concept for SWIFTUI. However, some applications still require the old style callbacks, used to notify some part of the system that a particular event happened in the UI. This article will show you a simple trick that will help you getting the best of the two words. It also solves a nasty deprecation warning introduced in beta5 of Xcode 11.

Dragos Ioneanu
The Startup
3 min readAug 11, 2019

--

Photo by Michał Kubalczyk on Unsplash

According to Apple documentation:

Use a binding to create a two-way connection between a view and its underlying model. For example, you can create a binding between a Toggle and a Bool property of a State. Interacting with the toggle control changes the value of the Bool, and mutating the value of the Bool causes the toggle to update its presented state.
You can get a binding from a State by accessing its binding property. You can also use the $ prefix operator with any property of a State to create a binding.

One of the initialisers of Binding is:

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

In this post, I will show you 2 very useful utilisations of this initializer, that will make your life easier.

Prior to beta4 of Xcode 11, it was fairly simple to bind a list of controls to elements of an array. However, in beta5, if you are using this, you will get a warning:

subscript(_:)’ is deprecated: See Release Notes for a migration path

And the release notes of beta5 give a fairly complicated way to avoid this deprecation. I will not detail here on this migration path, but you can read about it by searching the section starting with “Several extensions to the Binding structure are removed. (51624798)” in the above link.

However the get/set initialiser of Binding gives a much simpler way to overcome this issue.

Here is how to achieve this:
a. I define a @State array of names

@State var items:[String] = [ "John", "Bill", "Mary", "Pete",   "Jane", "Tom", "Caroline", "Mindy" ]

b. I loop on the array of names and create a TextField for each name in the array. I create a Binding for the TextField elements, where get is pointing to the correct position in the state array, while set is setting the new value in the state array. Here you go: no subscript error and a really simple way of achieving same thing as the migration path.

List{ 
ForEach(items.indices, id:\.self ){ idx in
TextField("", text: Binding(
get: {
return self.items[idx]
},
set: { (newValue) in
return self.items[idx] = newValue
}))
}
}

Extending the functionality of a control:

Take a look at Stepper control. It has a number of initialisers, but in terms of data interactions, they even get a binding for a two way link between data and the control or a pair of onIncrement/onDecrement callbacks. But not both at the same time.
However the set/get initialiser for Binding can actually provide both, and this is how:

a. I declare a @State of type Int

@State var numberOfDays: Int = 23

b. I create a Stepper control and instead of simply binding it with the state, using $, I use a set/get initialised Binding for my Stepper. In this way, the Stepper gets the value of the @State, the @State gets updated when stepper +/- buttons are used, but on top of this, I have an entry point in the set method to call additional methods for increment/decrement. Comparing the newValue with the state variable, gives the right information if the stepper action was an increment or a decrement. In this case I am printing this information, however more complicated logic can be used instead.

Stepper(value: Binding( 
get: {
return self.numberOfDays
},
set: { (newValue) in
if self.numberOfDays < newValue{
print("increase")
}
else{
print("decrease")
}
self.numberOfDays = newValue
}), in: 0...10000,
label: {
Text("Number of days: \(numberOfDays)")
}).padding()

Using the set/get initialiser for Binding is a very useful tool to get both a binding but also to react on the value change event for your control. There are obviously a large amount of utilisations; each developer can use this technique in various creative ways, to achieve the desired behaviour and to enhance the SwiftUI controls with functionality that they lack at the moment.

Originally published at http://codingpills.ioneanu.com on August 11, 2019.

--

--