Infinite view loop

If I add items at the root level, this code works, but if I attempt to add a child to any item, it runs an infinite loop where it goes from the AddItemView back to the SubItemView and starts all over. I suspect it has something to do with the Predicate in SubItemView, but the debugger is crap, so I'm just guessing.

Minimal code:

@Model
final class Item {
	var id: String
	var name: String
	var children: [Item] = []

	@Relationship(deleteRule: .cascade) var parent: Item?

	init(name: String, parent: Item?, id: String = UUID().uuidString) {
		self.name = name
		self.parent = parent
		self.id = id
	}
}
struct ContentView: View {
	@Environment(\.modelContext) private var modelContext
	@Query(
		filter: #Predicate<Item> {
			item in
			item.parent == nil
		}, sort: \Item.name
	) public var rootItems: [Item]

	var body: some View {
		NavigationStack {
			List {
				ForEach(rootItems) { item in
					HStack {
						NavigationLink ( destination: SubItemView(parent: item)) {
							Text(item.name)						}
					}
				}
			}
			.toolbar {
				ToolbarItem(placement: .navigationBarTrailing) {
					EditButton()
				}
				ToolbarItem {
					NavigationLink(destination: AddItemView(itemParent: nil)) {
						Text("Add To Do")
					}
				}
			}
		}
	}

}
struct SubItemView: View {
	@Environment(\.dismiss) private var dismiss
	@Environment(\.modelContext) private var modelContext
	@State var parent: Item
	@State private var todo: String = ""
	@State var selectedDate = Date()
	@State var showPicker: Bool = false
	@Query var children: [Item]

	init(parent: Item) {
		self.parent = parent

		let parentID = parent.id
		_children =  Query(filter: #Predicate<Item> {
			$0.parent.flatMap(\.id) == parentID && $0.parent.flatMap(\.id) != nil
		}, sort: \Item.name )
	}

	var body: some View {
		Form {
			LabeledContent {
				TextField("Name", text: $parent.name)
			} label: {
				Text("Name:")
			}
		}

		Text("Parent: \(parent.name)\n")
		NavigationStack {
			Text("Child count: \(children.count)")

			List(children) {  child in
					HStack {
						if(child.children.isEmpty) {
							Text(child.name)
							NavigationLink ( destination: SubItemView(parent: child)) {
								Text("").foregroundColor(.white).background(Color.blue)
							}
							.opacity(0)
							.background(
								Text("")
							)
						} else {
							Text(child.name)
							NavigationLink(destination: SubItemView(parent: child)) {
								Text("")
							}
						}
					}
				}
			}
			.navigationTitle("Sub Items")
			.toolbar {
				ToolbarItem(placement: .navigationBarTrailing) {
					EditButton()
				}
				ToolbarItem {
					NavigationLink(destination: AddItemView(itemParent: parent)) {
						Text("Add To Do")
					}
				}
				ToolbarItem {
					Button("Save") {
						try? modelContext.save()
						dismiss()
					}
				}
			}
		}
	}
struct AddItemView: View {
	@Environment(\.dismiss) private var dismiss
	@Environment(\.modelContext) private var context
	@State var itemParent: Item?
	@State private var name = ""
	@State private var showWarning: Bool = false
	@State var child = Item(name: "", parent: nil)

	var body: some View {

		NavigationStack {
			Form {
				LabeledContent {
					TextField("Item", text: $name)
				} label: {
					Text("Todo:")
				}
			}
			.navigationTitle("Add New Item")
			.toolbar {
				Button("Save") {
					let tmp = Item(name: name, parent: itemParent)
					if(itemParent != nil) {
						itemParent!.children.append(tmp)
					}
					context.insert(tmp)
					try? context.save()
					dismiss()
				}
			}
		}
	}
}

It wasn't the query, it was the children array in the Item model competing with the query.

I thought that I'd solved it by changing the AddItemView toolbar to this:

			.toolbar {
				Button("Save") {
					let tmp = Item(name: name, parent: itemParent)
					if self.itemParent == nil {
						context.insert(tmp)
					} else {
						itemParent?.children.append(tmp)
					}
					try? context.save()
					dismiss()
				}
			}

That works for the first layer, which is why the image below has items B and C, but clicking on them causes another infinite loop, this time with the SubItemView calling itself.

It looks like SwiftUI is pre-drawing the navigation links when the screen is loaded, and since one of those links is to the same screen with (usually) a different item, it gets repeated infinitely.

If anyone knows how to stop that without having to use a nearly identical view, please post it here.

I have to say that this is yet another serious flaw in the Swift framework. You have to write your code on the assumption that the initializer for a view may be called multiple times, but this is highly inefficient and it causes a problem where you can easily have race conditions. I see no way to have nested models where an item can link to an array of items, for example, because that requires a query in an initializer that can create an infinite loop.

Combined with Xcode's terrible debugging, this is infuriating.

Infinite view loop
 
 
Q