John
John

Reputation: 1467

Custom @Model property getter/setter in SwiftData

In Core Data I can implement a custom NSManagedObject property getter/setter:

@objc var name: String {
    get {
        willAccessValue(forKey: #keyPath(name))
        defer { didAccessValue(forKey: #keyPath(name)) }
        
        let name = primitiveValue(forKey: #keyPath(name)) as? String
        return name ?? ""
    }
    set {
        willChangeValue(forKey: #keyPath(name))
        defer { didChangeValue(forKey: #keyPath(name)) }
        
        // Trimming is just an example, it could be any data cleanup.
        let name = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
        setPrimitiveValue(name, forKey: #keyPath(name))
    }
}

How to achieve that in a SwiftData @Model? I don't want to create a second property based on a persisted one but have only a single property.

Why not 2 properties:

Upvotes: 1

Views: 612

Answers (1)

John
John

Reputation: 1467

There doesn't seem to be an easy solution using a single property. But it's possible to address the pain points.

Solution 1

@Attribute(originalName: "name")
private(set) var _name = ""

var name: String {
    get { _name }
    set { _name = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
  • @Attribute(originalName: "name") is only needed if you change the stored property name from name to _name and have data to migrate. If you forget this, the data will be deleted. ⚠️
  • _name is private(set) to ensure clean data via the name setter. It cannot be fully private because as a computed property name is not available in #Predicate.

Solution 2

private(set) var name = ""

var name2: String {
    get { name }
    set { name = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
  • The stored property retains its name, so no data migration is needed when you add the computed property later.
  • The "2" in name2 stands for two-way (get and set).
  • If you do any processing in the getter, you have to use name2 instead of name where possible, which is not as intuitive as using name over _name. ⚠️

Upvotes: 1

Related Questions