Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to optionally pass in a Binding in SwiftUI?

Tags:

swift

swiftui

I have a view that internally manages a @State variable that keeps track of the current index. So something like:

struct ReusableView: View {

    @State var index: Int = 0

    var body: some View {
        Text("The index is \(self.index)"
        // A button that changes the index
    }

}

This view is going to be reused throughout the app. Sometimes the parent view will need access to the index, so I refactored it like this:

struct ParentView: View {

    @State var index: Int = 0

    var body: some View {
      ReusableView($index)
    }

}

struct ReusableView: View {

    @Binding var index: Int

    var body: some View {
        Text("The index is \(self.index)"
        // A button that changes the index
    }

}

The problem

I don't want to enforce the parent view to always keep the state of the index. In other words, I want to optionally allow the parent view to be in charge of the state variable, but default to the Reusable View to maintain the state in case the parent view doesn't care about the index.

Attempt

I tried somehow to initialize the binding on the reusable view in case the parent view doesn't provide a binding:

struct ReusableView: View {

    @Binding var index: Int

    init(_ index: Binding<Int>? = nil) {
        if index != nil {
            self._index = index
        } else {
            // TODO: Parent didn't provide a binding, init myself.
            // ?
        }
    }

    var body: some View {
        Text("The index is \(self.index)"
        // A button that changes the index
    }

}

Thank you!

like image 742
McGuire Avatar asked Nov 21 '19 20:11

McGuire


People also ask

What is @binding in SwiftUI?

What Is @Binding? The view in SwiftUI may contain multiple child views. You may want to communicate with those child views or want a child view to change the value of its parent.

How to access string inside a UIView binding?

If you want to access the String inside your Binding, you have to use the wrappedValue property. Show activity on this post. I have a UIViewControllerRepresentable in order to use UIImagePickerController. If you've ever used this image picker, you know that you need to image returned to be an optional. So in my ContentView I declared:

Is it possible to make an optional string into a binding?

What you want is an Optional Binding of a String, not a Binding of an Optional String. I don't think you can achieve that by using the @Binding annotation. However, you don't need to used the annotation. You can just declare the variable as a Binding:

What is a binding in iOS?

In essence, a binding, as the name implies, is a property directive (or wrapper) that indicates a relationship between an object or value and the View that consumes it. As the Apple documentation states, “A binding connects a property to a source of truth stored elsewhere, instead of storing data directly.”


1 Answers

The main problem with what you want to achieve is that when the index is handled by the parent your View needs a @Binding to it, but when it handles the index itself it needs @State. There are two possible solutions.

If the view can ignore the index property when it doesn't have one:

struct ReusableView: View {

    @Binding var index: Int?

    init(_ index: Binding<Int?>) {
        self._index = index
    }

    init() {
       self._index = .constant(nil)
    }

    var body: some View {
        VStack {
            index.map { Text("The index is \($0)") }
        }
    }   
}

The advantage is that it is very straightforward - just two initializers, but you cannot change the value of the index when it is handled by ResusableView itself (its a constant).

If the view cannot ignore the index property when it doesn't have one:

struct ReusableView: View {

    private var content: AnyView

    init(_ index: Binding<Int>? = nil) {
        if let index = index {
            self.content = AnyView(DependentView(index: index))
        } else {
            self.content = AnyView(IndependentView())
        }
    }

    var body: some View {
        content
    }

    private struct DependentView: View {

        @Binding var index: Int

        var body: some View {
            Text("The index is \(index)")
        }
    }

    private struct IndependentView: View {

        @State private var index: Int = 0

        var body: some View {
            Text("The index is \(index)")
        }
    }

}

The clear advantage is that you have a view that can either be bound to a value or manage it as its own State. As you can see ReusableView is just a wrapper around two different views one managing its own @State and one being bound to @State of its parent view.

like image 86
LuLuGaGa Avatar answered Nov 11 '22 04:11

LuLuGaGa