SwiftData model self question

Here is a simple setup that demonstrates my issue. Problem is the prev attribute is not correct. I believe it is because when I change previousNode, this modifies the existing Node objects.

import SwiftUI
import SwiftData

@Model
class Node {
    var name: String
    var prev: Node?
    
    init(name:String, prev: Node?) {
        self.name = name
        self.prev = prev
    }
}

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query var nodes: [Node]
    
    // Keep track of the last made node to set as previous when creating new node...
    @State var previousNode: Node? = nil
    
    var body: some View {
        VStack {
            Button("Add node") {
                print("Adding node", nodes)
                let node = Node(name: "Node \(nodes.count)", prev: previousNode)
                modelContext.insert(node)
                do {
                    try modelContext.save()
                    previousNode = node
                } catch {
                    fatalError("Failed saving model context: \(error)")
                }
            }
            List(nodes) { node in
                HStack {
                    Text(node.name + " has previous node:")
                    Text(node.prev?.name ?? "nil")
                }
            }
        }
        .padding()
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Node.self, inMemory: true)
}
1 Like

SwiftData sort of makes the assumption that all of your lists are doubly-linked. They don't really have to be, but you need to make it think they are. :persevere:

@Model final class Node {
  var name: String
  var prev: Node?
  
  init(name: String, prev: Node?) {
    self.name = name
    self.prev = prev
  }

  /// This is always nil, but SwiftData doesn't know how to work without it.
  private var nilNode: Node?
}
1 Like

That works, thanks. Can you please explain further, or provide link to relevant documentation? I don't understand what it is doing without the extra Node, or what makes it work correctly with it there.

Here is the behaviour without it:

  • One
Node 0 has previous node: nil
  • Two
Node 0 has previous node: Node 1
Node 1 has previous node: Node 0
  • Three
Node 0 has previous node: nil
Node 1 has previous node: Node 2
Node 2 has previous node: Node 1
  • Four
Node 0 has previous node: nil
Node 1 has previous node: nil
Node 2 has previous node: Node 3
Node 3 has previous node: Node 2
1 Like

Oh, there's no documentation, and nobody who understands it will tell you anything about it without you paying a lot of money at rare developer events which are held in similarly expensive locations.

2 Likes

Is it the intended way, an extra Node property? An alternative approach is sorting the query, since in my case the previous are initially set by the creation time.

@Query(sort: \Node.createdAt) var nodes: [Node]