Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119292

How to use @State if @Binding not provided in the initializer

Imagine a view with some @Binding variables:

init(isEditing: Binding<Bool>, text: Binding<Bool>)

How can we have the selection working with an internal @State if it is not provided in the initializer?

init(text: Binding<Bool>)

This is how to make TextField become first responder in SwiftUI

Note that I know we can pass a constant like:

init(isEditing: Binding<Bool> = .constant(false), text: Binding<Bool>)

But!

This will kill the dynamicity of the variable and it won't work as desire. Imagine re-inventing the isFirstResponder of the UITextField.

Maybe! Apple is doing it somehow with TabView.

Upvotes: 4

Views: 558

Answers (2)

pawello2222
pawello2222

Reputation: 54466

You may create separate @State and @Binding properties and sync them using onChange or onReceive:

struct TestView: View {
    @State private var selectionInternal: Bool
    @Binding private var selectionExternal: Bool

    init() {
        _selectionInternal = .init(initialValue: false)
        _selectionExternal = .constant(false)
    }

    init(selection: Binding<Bool>) {
        _selectionInternal = .init(initialValue: selection.wrappedValue)
        _selectionExternal = selection
    }

    var body: some View {
        if #available(iOS 14.0, *) {
            Toggle("Selection", isOn: $selectionInternal)
                .onChange(of: selectionInternal) {
                    selectionExternal = $0
                }
        } else {
            Toggle("Selection", isOn: $selectionInternal)
                .onReceive(Just(selectionInternal)) {
                    selectionExternal = $0
                }
        }
    }
}
struct ContentView: View {
    @State var selection = false

    var body: some View {
        VStack {
            Text("Selection: \(String(selection))")
            TestView(selection: $selection)
            TestView()
        }
    }
}

Upvotes: 3

Lineous
Lineous

Reputation: 1782

One solution is to pass an optional binding and use a local state variable if the binding is left nil. This code uses a toggle as an example (simpler to explain) and results in two interactive toggles: one being given a binding and the other using local state.

import SwiftUI

struct ContentView: View {
    
    @State private var isOn: Bool = true
    
    var body: some View {
        VStack {
            Text("Special toggle:")
            SpecialToggle(isOn: $isOn)
                .padding()
            SpecialToggle()
                .padding()
        }
    }
    
}

struct SpecialToggle: View {
    
    /// The binding being passed from the parent
    var isOn: Binding<Bool>?
    /// The fallback state if the binding is left `nil`.
    @State private var defaultIsOn: Bool = true
    
    /// A quick wrapper for accessing the current toggle state.
    var toggleIsOn: Bool {
        return isOn?.wrappedValue ?? defaultIsOn
    }
    
    init(isOn: Binding<Bool>? = nil) {
        if let isOn = isOn {
            self.isOn = isOn
        }
    }
    
    var body: some View {
        Toggle(isOn: isOn ?? $defaultIsOn) {
            Text("Dynamic label: \(toggleIsOn.description)")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Optional binding toggle.

Upvotes: 2

Related Questions