Crash using Binding.init?(_: Binding<Value?>)

In the example code below:

  • OuterView has a @State var number: Int? = 0
  • InnerView expects a binding of type Int.
  • OuterView uses Binding.init(_: Binding<Value?>) to convert from Binding<Int?> to Binding<Int>?.
  • If OuterView sets number to nil, there is a runtime crash:
BindingOperations.ForceUnwrapping.get(base:) + 160

InnerView is being evaluated (executing body?) when number is nil despite the fact that it shouldn't exist in that configuration. Is this a bug or expected behavior?

struct OuterView: View {
  @State var number: Int? = 1

  var body: some View {
    if let binding = Binding($number) {
      InnerView(number: binding)
    }

    Button("Doesn't Crash") { number = 0 }
    Button("Crashes") { number = nil }
  }
}

struct InnerView: View {
  @Binding var number: Int

  var body: some View {
    Text(number.formatted())
  }
}

There is a workaround that involves adding an extension to Optional<Int>:

extension Optional<Int> {
  var nonOptional: Int {
    get { self ?? 0 } 
    set {  self = newValue }
  }
}

And using that to ensure that the binding has a some value even when number is nil.

if number != nil {
  InnerView(number: $number.nonOptional)
}

This works, but I don't understand why it's necessary. Any insight would be greatly appreciated!

I have had issues in the past when using the Binding initialisers that accept another Binding. According to the error message, it is force unwrapping the value inside instead of returning nil from the initialiser itself.

I find creating your own Binding seems to work for most cases.

if let number {
    InnerView(number: .init {
        number
    } set: {
        self.number = $0
    })
}

The safe way to do this is to pass the optional binding down to the child view and optional unwrap the value when needed. For example:

struct ContentView: View {
  @State private var number: Int? = 1

  var body: some View {
      InnerView(number: $number)

    Button("Doesn't Crash") { number = 0 }
    Button("Crashes") { number = nil }
  }
}

struct InnerView: View {
  @Binding var number: Int?

  var body: some View {
      if let number = number {
          Text(number.formatted())
      }
  }
}

This way when the value is updated or nil, SwiftUI redraws the view to reflect the new state.

Crash using Binding.init?(_: Binding&lt;Value?&gt;)
 
 
Q