SwiftUI infinite rendering loop when using a custom Binding to a dictionary-based store

I’m building a SwiftUI app where the struct AppGroup is identified by a UUID and stored in a dictionary. My Task model has appGroupId: UUID?. In TaskDetailView, I create a custom Binding<AppGroup> from the store, then navigate to AppGroupDetailView. However, when I tap the NavigationLink, the console spams logs, CPU hits 100%, and it never stabilizes.

Relevant Code

AppGroupStore (simplified)

class AppGroupStore: ObservableObject {
    @Published var appGroupsDict: [UUID: AppGroup] = [:]

    func updateAppGroup(_ id: UUID, appGroup: AppGroup) {
        appGroupsDict[id] = appGroup
    }

    // Returns a binding so views can directly read/write the AppGroup by id
    func getBinding(withId id: UUID?) -> Binding<AppGroup> {
        Binding(
            get: {
                if let id = id {
                    return self.appGroupsDict[id] ?? .empty
                }
                return .empty
            },
            set: { newValue in
                print("New value set for \(newValue.name)")
                self.updateAppGroup(newValue.id, appGroup: newValue)
            }
        )
    }
    
    // ...
}

AppGroup is a simple struct:

struct AppGroup: Identifiable, Codable {
    let id: UUID
    var name: String
    var apps: [String]
    static let empty = AppGroup(id: UUID(), name: "Empty", apps: [])
}

TaskDetailView (main part)

struct TaskDetailView: View {
    @Binding var task: ToDoTask   // has task.appGroupId: UUID?
    @EnvironmentObject var appGroupStore: AppGroupStore
    
    var body: some View {
        let appGroup = appGroupStore.getBinding(withId: task.appGroupId)
        print("Task load") // prints infinitely, CPU 100%
        
        return List {
            // ...
            NavigationLink(destination: AppGroupDetailView(appGroup: appGroup)) {
                Text(appGroup.wrappedValue.name)
            }
        }
        .navigationTitle(task.name)
    }
}

AppGroupDetailView (simplified)

struct AppGroupDetailView: View {
    @Binding var appGroup: AppGroup
    // ...
    var body: some View {
        List {
            ForEach(appGroup.apps, id: \.self) { app in
                Text(app)
            }
        }
        .navigationTitle(appGroup.name)
    }
}

Symptoms:

  • Tapping the NavigationLink leads to infinite “Task load” logs and 100% CPU usage.
  • The set closure (“New value set for...”) is never called, so it’s not repeatedly writing.
  • If I replace the Binding<AppGroup> with a read-only approach (just accessing the dictionary), it does not get stuck.

Question:
What might cause SwiftUI to keep re-rendering the body indefinitely, even if my custom get closure doesn’t explicitly mutate the state? Are there known pitfalls when using a dictionary-based store and returning a Binding like this?

Any help is much appreciated!


Thanks in advance for your insights!

SwiftUI infinite rendering loop when using a custom Binding to a dictionary-based store
 
 
Q