TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SwiftUI: Grandparent View Not Updating on Model Change in SwiftData

Forums > SwiftUI

Posted this on StackOverflow as well.

I'm working on a SwiftUI app using SwiftData for state management. I've encountered an issue with view updates when mutating a model. Here's a minimal example to illustrate the problem:

import SwiftUI
import SwiftData

// MARK: - Models

@Model
class A {
  @Relationship var bs: [B] = [B]()

  init(bs: [B]) {
    self.bs = bs
  }
}

@Model
class B {
  @Relationship var cs: [C] = [C]()

  init(cs: [C]) {
    self.cs = cs
  }
}

@Model
class C {
  var done: Bool

  init(done: Bool) {
    self.done = done
  }
}

// MARK: - Views

struct CView: View {
  var c: C

  var body: some View {
    @Bindable var c = c
    HStack {
      Toggle(isOn: $c.done, label: {
        Text("Done")
      })
    }
  }
}

struct BView: View {
  var b: B

  var body: some View {
    List(b.cs) { c in
      CView(c: c)
    }
  }
}

struct AView: View {
  var a: A

  var body: some View {
    List(a.bs) { b in
      NavigationLink {
          BView(b: b)
      } label: {
        Text("B \(b.cs.allSatisfy({ $0.done }).description)")
      }
    }
  }
}

struct ContentView: View {
  @Query private var aList: [A]

  var body: some View {
    NavigationStack {
      List(aList) { a in
        NavigationLink {
          AView(a: a)
        } label: {
          Text("A \(a.bs.allSatisfy({ $0.cs.allSatisfy({ $0.done }) }).description)")
        }
      }
    }
  }
}

@main
struct Minimal: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

#Preview {
  let config = ModelConfiguration(isStoredInMemoryOnly: true)
  let container = try! ModelContainer(for: A.self, configurations: config)

  var c = C(done: false)
  container.mainContext.insert(c)
  var b = B(cs: [c])
  container.mainContext.insert(b)
  var a = A(bs: [b])
  container.mainContext.insert(a)

  return ContentView()
    .modelContainer(container)
}

In this setup, I have a CView where I toggle the state of a model C. After toggling C and navigating back, the grandparent view AView does not reflect the updated state (it still shows false instead of true). However, if I navigate back to the root ContentView and then go to AView, the status is updated correctly.

Why doesn't AView update immediately after mutating C in CView, but updates correctly when navigating back to the root ContentView? I expected the grandparent view to reflect the changes immediately as per SwiftData's observation mechanism.

I also recorded a gif of the flow: imgur.com/a/d6SwJSD

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.