Why is @Binding not behaving as I expect here?

Hi all!

I am trying to understand why the Stepper's title, where model.count.formatted() is passed in, doesn't update when I tap the buttons on the Stepper? (Yet the model.count is updated on the struct Row: View?)

I know this may not be the optimal code, but I'm trying to understand how @Binding behaves in different scenarios.

Thanks!

import SwiftUI

// MARK: - Model

struct Model {
  let title: String
  var count: Int
}

extension Model: Identifiable {
  var id: String {
    title
  }
}

// MARK: - StateProvider

final class StateProvider: ObservableObject {
   
  @Published
  var data: [Model] = [
    Model(title: "Elephant", count: 3),
  ]
}

// MARK: - Detail View

struct DetailView: View {
   
  @Binding
  var model: Model
   
  var body: some View {
    ScrollView {
      Text(model.title)
        .font(.title)
      Stepper(model.count.formatted()) {
        model.count += 1
      } onDecrement: {
        model.count -= 1
      }
      .padding()
    }
  }
}

// MARK: - List View

struct Row: View {
   
  @Binding
  var model: Model
   
  var body: some View {
    HStack {
      Text(model.title)
      Spacer()
      Text(model.count.formatted())
    }
  }
}

struct ContentView: View {
   
  @StateObject
  private var stateProvider = StateProvider()
   
  var body: some View {
    NavigationStack {
      List {
        ForEach($stateProvider.data) { model in
          NavigationLink {
            DetailView(model: model)
          } label: {
            Row(model: model)
          }
        }
      }
    }
  }
}

// MARK: - Preview

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

My initial thoughts are the issue is caused by NavigationLink?

Or, perhaps I am missing an aspect of how bindings behave on an observable object's list? Specifically where I pass $stateProvider.data?

FYI—The following code works using an ObservableObject pattern—just curious why the @Binding doesn't work?

import SwiftUI

// MARK: - Model

final class Model: ObservableObject {
   
  let title: String
   
  @Published
  var count: Int
   
  init(title: String, count: Int) {
    self.title = title
    self.count = count
  }
}

extension Model: Identifiable {
  var id: String {
    title
  }
}

// MARK: - StateProvider

final class StateProvider: ObservableObject {
   
  @Published
  var data: [Model] = [
    Model(title: "Elephant", count: 3),
  ]
}

// MARK: - Detail View

struct DetailView: View {
   
  @EnvironmentObject
  private var model: Model
   
  var body: some View {
    ScrollView {
      Text(model.title)
        .font(.title)
      Stepper(model.count.formatted()) {
        model.count += 1
      } onDecrement: {
        model.count -= 1
      }
      .padding()
    }
  }
}

// MARK: - List View

struct Row: View {
   
  @EnvironmentObject
  private var model: Model
   
  var body: some View {
    HStack {
      Text(model.title)
      Spacer()
      Text(model.count.formatted())
    }
  }
}

struct ContentView: View {
   
  @StateObject
  private var stateProvider = StateProvider()
   
  var body: some View {
    NavigationStack {
      List {
        ForEach(stateProvider.data) { model in
          NavigationLink {
            DetailView()
              .environmentObject(model)
          } label: {
            Row()
              .environmentObject(model)
          }
        }
      }
    }
  }
}

// MARK: - Preview

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

Now I think about it, the source of truth when using the @Binding is the final class StateProvider: ObservableObject {}, right? And since the DetailView doesn't reference the stateProvider, the DetailView doesn't get reloaded when the Model updates?

Now, by using an ObservableObject for the Model, the source of truth changes to being the Model instead of the state provider, therefore the view responds to updates?

Could someone please verify if this line of thinking is correct?

Thanks!

I've also found that swiftUI's refreshing had gone wrong somehow

if I do this:

List($someSortOfADynamicList) { $item in
    TextField("Placeholder...", text: $item.someSortOfAStringVariable)
}

it'll act very weird. Every time a value is changed, the current textfield focused defocused. And if you use a NavigationView, the current NavigationLink gets unfocused, that is, no view displayed in the destination page.

@Binding is not the right property wrapper to declare an ObservableObject inside a View. Use @ObservedObject or @StateObject to do that. @Binding is used for value type variables.

see https://www.hackingwithswift.com/quick-start/swiftui/all-swiftui-property-wrappers-explained-and-compared

I just checked the code from the first post, but it seems to work here without any change 🤔 Xcode 14.0 beta 6 (14A5294g) in the iPhone 12 Pro simulator.

Why is @Binding not behaving as I expect here?
 
 
Q