Multiple levels of Bindings don't update properly

If I have a NavigationView, and a root view with an `Int` in its model, I can push other views with a Binding<Int> to that model, and if any of the pushed views updates the Int, everything re-renders as expected. But this only works if the root's model is State<Int>. If instead I have an `ObservableObject` with an Int property, it doesn't work. The first pushed view can change the Int, and it will update on screen, but further pushed Views do not update properly.


Example code is below. It will work correctly if you use the commented-out lines with @State, instead of the @ObservedObject.


As is, if you push two levels, and press "Increment Number", the screen doesn't update. (It does if you used @State at the root).


Is this a known bug in SwiftUI? Shouldn't the Bindings work the same in both cases?


class MyObject: ObservableObject {
    @Published var number: Int = 1
}
struct ContentView: View {
    // This one works
    // @State private var number: Int = 1
    // What about:
    @ObservedObject var myObject: MyObject
    
    var body: some View {
        NavigationView {
            VStack {
                // Text("number: \(number)")
                Text("number: \(myObject.number)")
                NavigationLink(destination: PushedView(number: $myObject.number, pushLevel: 1)) {
                    Text("Push a View")
                }
            }
        }
    }
}

struct PushedView: View {
    @Binding var number: Int
    let pushLevel: Int
    func incrementIt() {
        number += 1
    }
    var body: some View {
        VStack {
            Text("Pushed View (level \(pushLevel))")
            Text("number (via binding): \(number)")
            Button(action: self.incrementIt) {
                Text("Increment number")
            }
            NavigationLink(destination: PushedView(number: $number, pushLevel: pushLevel+1)) {
                Text("Push a View")
            }
        }
    }
}

Replies

Hi,


I am sure I can't explain it in a good way, but I think the issue is, that you are passing on the @Binding and not the the object itself (as you do in the ContentView) as the single source of truth. I think the best way is probably to pass on the model object (MyObject) itself. See the modified coding below.

And yes, I think more in depth documentation about SwiftUI and the concepts would be helpful here ;-).


HTH, Michael


class MyObject: ObservableObject {
  @Published var number: Int = 1
}

struct ContentView: View {
  @ObservedObject var myObject: MyObject

  var body: some View {
    NavigationView {
      VStack {
        Text("number: \(myObject.number)")
        NavigationLink(destination: PushedView(obj: myObject, pushLevel: 1)) {
          Text("Push a View")
        }
      }
    }
  }
}

struct PushedView: View {
  @ObservedObject var obj: MyObject
  let pushLevel: Int
  func incrementIt() {
    obj.number += 1
  }

  var body: some View {
    VStack {
      Text("Pushed View (level \(pushLevel))")
      Text("number (via binding): \(obj.number)")
      Button(action: self.incrementIt) {
        Text("Increment number")
      }
      NavigationLink(destination: PushedView(obj: obj, pushLevel: pushLevel + 1)) {
        Text("Push a View")
      }
    }
  }
}

Hi Michael,

I posted on the swift.org forums about this and someone answered saying it was a bug related to NavigationLink, and there is a radar for it. So apparently my example code _should_ work, regardless of whether the "source of truth" is a State or an ObservedObject. But I think what you did passing the object is a good work-around.

Rob