swiftuiicloudcloudkitswiftdata

Why isn't SwiftData updating the items in a ForEach loop when synced with iCloud but it does if just used with a List


I have an app that uses SwiftData which is set to be synced with iCloud.

When I run the app in the simulator and on an actual device simultaneously and then amend the customer name on the simulator the change is not reflected on the device if the records are within a ForEach loop within a List. But if just within a List then it does sync and update automatically.

This updates automatically:

List(customers) { cust in
      NavigationLink(cust.name, value: cust)
}

but this doesn't:

List {
    ForEach(customers) { customer in
      NavigationLink(customer.name, value: customer)
}

I realize that notifications only work one way from the simulator (which is fine for my test) but I am getting back data from iCloud when I make the change so it appears as if iCloud is sending the notification but the UI isn't reflecting this within a ForEach loop.

Inserts and deletions do work when used with both methods above.

So, why does it update correctly within a List but not within a Foreach loop?


Solution

  • According to my understanding this is a bug in SwiftData/SwiftUI, but as mentioned in my comment, there is a workaround:

    It seems that the ForEach view (in contrast to the List) is not re-rendering its body whenever the @Query result is refreshed. It seems that ForEach does not detect any relevant change and therefore is not rendering again. To enforce re-rendering of the ForEach content you can add a onChange modifier to your "Row view" to track changes to the Query. You do not have to handle anything in the modifiers closure. Just the presence of the modifier ensures that SwiftUI is rendering the rows again.

    List {
        ForEach(customers) { customer in
          NavigationLink(customer.name, value: customer)
              .onChange(of: customers) {}
        }
    }