SwiftUI Binding Extensions. Making binding in SwiftUI views and… | by Michael Long | Nov, 2022

Binding to SwiftUI Views and Previews is as easy as ABC

photo by Sigmund Feather unsplash

Here are some common SwiftUI problems that fall into the “there must be a better way” category.

Let’s start with the following amazingly complex SwiftUI views.

struct AmazinglyComplexView: View 

@Binding var value: Bool

var body: some View
Toggle(isOn: $value)
Text("Toggle Me")

.padding()

here we have a scene Toggle that has a bond to some value. Now we want to preview our view. easy right?

struct AmazinglyComplexView_Previews: PreviewProvider 
static var previews: some View
AmazinglyComplexView(value: .constant(true))

Since we have a binding, we follow SwiftUI conventions and pass .constant(true) to the binding parameter of our view. And once we do that we see a preview of our approach.

sample preview

However, there’s only one problem. Xcode 14 preview is live now… but we can’t toggle our view and see our switch change because we passed in a constant value to our bindings. Nothing happens when the switch is pressed.

What to do?

well if you check stackoverflow you’ll see that the general consensus is to wrap our view another scene in which a @State Define variable, and then pass binding He Value for our consideration.

You know, the scene we wanted to test first. it looks like this.

struct AmazinglyComplexView_Previews: PreviewProvider 
struct Wrapper: View
@State var value: Bool
var body: some View
AmazinglyComplexView(value: $value)


static var previews: some View
Wrapper(value: true)

In fact, this is actually the approach recommended by Apple. WWDC 2020 Developer Videos,

Either way, the result is the same, and our preview works.

But in reality? Do we have to go through this kind of rigamarole every time and every time we want to preview a scene that has a binding?

I think we can try to show our SwiftUI-fun and write some kind of general idea. something that wraps our preview view into a ViewBuilder and creates a state object of the appropriate type. Maybe something like…

struct BindableWrapper: View 
@State var value: Value
let content: (_ value: Binding) -> Content
init(value: Value, @ViewBuilder content: @escaping (_ value: Binding) -> Content)
_value = .init(initialValue: value)
self.content = content

var body: some View
content($value)

struct AmazinglyComplexView_Previews: PreviewProvider
static var previews: some View
BindableWrapper(value: true)
AmazinglyComplexView(value: $0)


And bingo! We have one more solution. types of. I mean, at least now we don’t have to create a wrapper every time we want to test some view with the same binding…

Oh. cat.

What if we need to test a view with two bindings? or three? Do we start writing bindable wrappers with bindings one and two and three and four?

There must be a better way.

And there it is.

SwiftUI gets very close to providing what we need .constant,

But as you probably know, the only stops are horseshoes, hand grenades, and when your deodorant stops working. So with that in mind allow me to introduce .variable,

Add the following extension to your code.

extension Binding 
public static func variable(_ value: Value) -> Binding
var state = value
return Binding
state
set:
state = $0


Here we create an extension function that assigns our initial value to an internal state variable. The binding’s get and set closures capture that state variable and mutate it as needed.

And voila! Now when you want to test your scene in Preview, just do it.

struct AmazinglyComplexView_Previews: PreviewProvider 
static var previews: some View
AmazinglyComplexView(value: .variable(true))

and now we can toggle our views Toggle to our heart’s content.

2 toggle preview

even better, with .variable We can use many of them and as many times as necessary.

It’s common to have optional values ​​in our code due to API requirements, but working with them in SwiftUI can be a bit tricky. Consider.

class SomeViewModel: ObservableObject 
@Published var name: String?

struct SomeView: View
@StateObject var viewModel = SomeViewModel()
var body: some View
TextField("Name", text: $viewModel.name) // does not compile

Here we have a view model with an optional name string and we want to be able to edit that value. Unfortunately, there is no optional initializer for TextFieldBinding and the Swift compiler will give us an error. “Cannot convert value of binding type‘ binding to the expected argument type,

What to do? i mean, if it was a Text See we’ll just use a nil coalescing operator to provide a default value for the string in question.

But how do we provide a default value to the binding?

you guessed it. Here is the code for detail.

extension Binding 
public func defaultValue(_ value: T) -> Binding where Value == Optional
Binding
wrappedValue ?? value
set:
wrappedValue = $0


And now, instead, we just do.

struct SomeView: View 
@StateObject var viewModel = SomeViewModel()
var body: some View
TextField("Name", text: $viewModel.name.defaultValue(""))

All we have to do is give our string a default value and we’re good to go.

And since DefaultValue is generic, it works with strings, integers, bools… whatever we need.

If we work with a lot of strings and if we’re always defaulting them to empty, we can borrow a concept that was used by RxSwift This Alternate text binding needs to be done.

Let’s add one more detail to our repertoire.

extension Binding where Value == Optional 
public var orEmpty: Binding
Binding
wrappedValue ?? ""
set:
wrappedValue = $0


which allows…

struct SomeView: View 
@StateObject var viewModel = SomeViewModel()
var body: some View
TextField("Name", text: $viewModel.name.orEmpty)

Small but sweet.

So there you have it. Nothing terrible today, just some common SwiftUI binding problems we often encounter in our code, and three of my favorite binding extensions that let us whittle them down to size.

Got your favourite? Tell me about them in the comments below.

Till next time.

Leave a Reply