James Clarke
James Clarke

Reputation: 2747

SwiftUI: Switching from @State to @Binding causes compilation error

I'm new to SwiftUI and still learning so this maybe obvious. The difference between the two is changing from @State to @Binding. There is clearly magic happening that was working for the first case but not working for the second case. Any insights greatly received.

Compiles fine:

struct PointEditorView: View {
  @State var sliderPosition: ClosedRange<Float> = 3...8

  var body: some View {
     Text("PointEditorView")
  }
}

Fails to compile:

struct PointEditorView: View {
  @Binding var sliderPosition: ClosedRange<Float> = 3...8

  var body: some View {
     Text("PointEditorView")
  }
}

This second case results in two errors:

  1. Incorrect argument label in call (have 'wrappedValue"; expected 'projectedValue:'
  2. Cannot convert value of type 'ClosedRanged to specified type 'Binding<ClosedRange>'

Upvotes: 1

Views: 132

Answers (2)

Sweeper
Sweeper

Reputation: 273540

As far as the language is concerned, this is just how property wrappers (@State, @Binding and others) work.

@Foo var foo: SomeType = bar

declares 2 or 3 properties all at once. It lowers to:

private var _foo = Foo(wrappedValue: bar)
var foo: SomeType { 
    get { _foo.wrappedValue }
    set { _foo.wrappedValue = newValue }
}

// only when Foo has a projectedValue property
var $foo: AnotherType { 
    get { _foo.projectedValue }
    set { _foo.projectedValue = newValue }
}

A stored property storing an instance of the property wrapper itself, and computed properties wrapping wrappedValue and projectedValue.

Notice how the stored property is initialised. This requires the property wrapper to have an initialiser taking a single parameter, with the parameter label wrappedValue:. @State has such an initialiser. @Binding does not.

@Binding does have an initialiser with a single parameter label projectedValue:, taking another Binding. That's why the confused compiler emits errors saying the parameter label is incorrect, and that the argument cannot be converted to Binding.

If you remove the initialiser from the declaration:

@Binding var sliderPosition: ClosedRange<Float>

Then _sliderPosition will not be initialised with Binding(wrappedValue: ...) (which is invalid), and so there is no problem. This generates a memberwise initialiser that takes a Binding and initialises _sliderPosition to the initialiser parameter.

Recommended reading: Initialization of synthesized storage properties section of the Swift Evolution Proposal for property wrappers.


From a SwiftUI point of view, Binding represents a "binding" to some source of truth (or another Binding), like a @State property, a @Published property of an ObservableObject, etc. A @Binding property is designed to be uninitialised, so that parent views can make a Binding that binds to some state of the parent, and pass it to the child.

If you want a Binding that wraps a constant value, use Binding.constant(...). You do not need the property wrapper.

let sliderPosition: Binding<ClosedRange<Float>> = .constant(3...8)

Upvotes: 0

yonodactyl
yonodactyl

Reputation: 220

The issue with your second piece of code is because @Binding works differently than @State. While @State creates and owns the data, @Binding does not - its meant to be a reference to data owned by another part of your app, in your case the PointEditorView is the owner

To fix the second example, you need to pass the sliderPosition as a Binding from a parent view, like this:

struct ParentView: View {
    @State private var sliderPosition: Float = 3.0

    var body: some View {
        PointEditorView(sliderPosition: $sliderPosition)
    }
}

struct PointEditorView: View {
    @Binding var sliderPosition: Float

    var body: some View {
        VStack {
            Text(sliderPosition.description)
            Slider(value: $sliderPosition, in: 3...8)
        }
    }
}

Here, ParentView owns the state and passes a binding reference to PointEditorView. I placed it in a VStack so that we can vertically stack the Views and see the changes.

Upvotes: 0

Related Questions