Dear Apple Developer Forum
As the title suggests, I have an issue with Swift Data when I want to modify a property of a recursive model class instance. Please consider the following sample project:
import SwiftUI
import SwiftData
@main
struct ISSUEApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
@Model
final class Item {
var name: String?
var parent: Item?
init(name: String?, parent: Item?) {
self.name = name
self.parent = parent
}
}
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query private var items: [Item]
@State private var itemToMove: Item?
@State private var count: Int = 0
@State private var presentMoveView: Bool = false
var body: some View {
NavigationStack() {
List(items, id: \.id) {item in
Button(action: {
itemToMove = item
}, label: {
Text("Id: \(item.name ?? "ERROR") and my parent iD is \(item.parent?.name ?? "root")")
.bold(itemToMove == item)
.italic(itemToMove == item)
})
}
.sheet(isPresented: $presentMoveView, content: {
MoveView(toMove: self.itemToMove!)
})
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
let i = Item(name: "\(count)", parent: nil)
context.insert(i)
try? context.save()
count += 1
}, label: {
Text("Add an item")
})
}
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentMoveView.toggle()
}, label: {
Text("Move selected item")
})
}
}
}
}
}
struct MoveView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@Query private var items: [Item]
@Bindable var toMove: Item
@State private var selectedFutureParent: Item?
var body: some View {
NavigationStack(){
List(items, id: \.id) {item in
Button(action: {
selectedFutureParent = item
}, label: {
Text("Id: \(item.name ?? "ERROR") and my parent iD is \(item.parent?.name ?? "root")")
.bold(selectedFutureParent == item)
.italic(selectedFutureParent == item)
})
}
.toolbar(){
ToolbarItem{
Button("Move", action: {
toMove.parent = selectedFutureParent
dismiss()
})
}
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
Please launch the preview of this app, add items (as many as you'd like), select one and click on the "Move selected item" button. Select the new parent of the item. As you may have noticed, both selected items (moved item and new parent) are modified, whereas only one equality is used. This issue seems to be independent from the @Bindable property wrapper. I tried many things, such as using index instead of direct elements; using local let constant for the parent but the constant is still modified (very weird...)
Thank you in advance for your help !
Best regards