SwiftUI binding dynamic array problem

I have an editor view that's broken up into several subviews. The model is a relatively small struct, but it does have a couple properties that can be variable-length arrays.


The problem I'm having is after I add one entry to any one of these arrays, the view stops updating:


struct ChecksView: View {
  @Binding var checks: [Check]

  var body: some View {
    HStack(alignment: .top, spacing: 0) {
      Button("add prereq", action: {
        self.checks.append(Check())
      })
      VStack {
        ForEach(self.checks.indices, id: \.self) { i in
          CheckView(check: self.$checks[i])
        }
      }
    }
  }
}


In this subview, the first time you tap "add prereq", it will add to the array & update the UI. But every subsquent time, nothing updates until you navigate away & return. What am I doing wrong?

Replies

hi,


you did not show us the definition of Check or CheckView or ContentView (if ChecksView is not the opening screen), but i put together your code as it stands with some simple assumptions and came up with the following, that works as (i think) expected and does not show the behaviour you suggest:


struct Check {
  var date: String = "Date"
  var name: String = "Name"
  var amount: Double = 0.0
}

struct ContentView: View {
  @State var checks = [Check]()
  var body: some View {
    VStack {
      Text("Check count is \(checks.count)")
      Divider()
      ChecksView(checks: $checks)
    }
  }
}

struct ChecksView: View {
  @Binding var checks: [Check]
  var body: some View {
    HStack(alignment: .top, spacing: 0) {
      Button("add prereq", action: {
        self.checks.append(Check())
      })
      VStack {
        ForEach(self.checks.indices, id: \.self) { i in
          CheckView(check: self.$checks[i])
        }
      }
    }
  }
}

struct CheckView: View {
  @Binding var check: Check
  var body: some View {
    HStack {
      Text(check.date)
      Spacer()
      Text(check.name)
      Spacer()
      Text(String(check.amount))
     }
  }
}


i'm running XCode 11.5 on MacOS 10.15.4 and simulator running iOS 13.5. could there be a problem elsewhere in code you are not showing?


hope that helps,

DMG

Hmm, ok, that suggests to me that the problem isn't in this construction itself, but something further up.


As I hinted, but should've made more explicit, the [Check] is a property on another struct. The larger structure has some pretty deeply-nested subviews, too much to put in a forum post, so I was hoping this small snippet would be enough to highlight what was going wrong. It wasn't – but since this works, I'm thinking I might be able to slowly grow out from this & poinpoint the problem.

hi greay,


if you have something out on Github, or one of the other hosting sites, i would take a look at it.


i've been frustrated myself several times by this general problem: data structure A changes and, even though you have some combination of @State, @Binding, @ObservableObject, @ObservedObject, and @Published scattered around in your code, a view that relies on A does not appear to update. your situation of "first time works, the others don't" is particularly puzzling.


looking forward to anything you might post, and hope that helps,

DMG

Hey, greay, did you ever find a solution for your problem? I'm having a similar problem with an app I'm working on where I have a one-and-done binding that functions as expected the first time, but then doesn't fire again upon changes. Any guidance would be greatly appreciated. :)

Hey, greay, did you ever find a solution for your problem? I'm having a similar problem with an app I'm working on where I have a one-and-done binding that functions as expected the first time, but then doesn't fire again upon changes. Any guidance would be greatly appreciated. :)

No, I ended up moving completely away from SwiftUI because I need to support older versions of macOS.

One thing I noticed here is that this seems wrong:

ForEach(self.checks.indices, id: \.self) { i in
          CheckView(check: self.$checks[i])
}

You already have the individual check captured in the i parameter when you do { i in }. So you don't need to do $checks[i]

You cans simply do:

ForEach(checks, id: \.self) { check in
          CheckView(check: check)
}

You also don't need the indices, you have access to the complete object inside your array within the closure. Furthermore, if you make your check model conform to Identifiable and Hashable (easily done with let id = UUID()) then you don't need the , id: \.self bit either.