G. Marc
G. Marc

Reputation: 6037

Trimming core data field before saving

I'd like to have the name field of a core data object be trimmed before it's saved to the context. My idea was to use the validation function for this. The following implementation works:

@objc
public func validateName(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
    guard let trimmedValue = (value.pointee as? String)?.trimmingCharacters(in: .whitespaces) else {
        return
    }

    if name != trimmedValue {
        name = trimmedValue
    }
}

Without the if statement which sets the name field only in case of a change, core data throws an exception because of a cycle.

Now I have to questions:

  1. Is the validation function the best way of doing this?
  2. The above code is quite verbose if you have multiple objects with fields that have to be trimmed. I've tried to create a function trimField with an inout variable value and moved the code for trimming, checking for a change and setting the inout variable. But when I call it from the validation function, an exception is thrown because of a cycle. So it seems that a function with inout parameter does set the field value to "dirty" even if it's not set explicitly. Are there any other options?

Upvotes: 0

Views: 107

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70976

Validation does this because, as you've found, any time you set a value, the validation function gets called. To change a value while validating, you need to bypass validation using setPrimitiveValue(_:forKey:) instead of setting name as usual. So, setPrimitiveValue(trimmed, forKey: "name") or similar.

Validation is probably not the best time to do this. You'll need to refresh any views displaying the object data, for example. There are some other options.

One is via didChangeValue, something like this:

public override func didChangeValue(forKey key: String) {
    if key == "name", let name = self.name {
        let trimmedValue = nametrimmingCharacters(in: .whitespaces)
        setPrimitiveValue(trimmedValue, forKey: "name")
    }
    super.willChangeValue(forKey: key)
}

You could also use willSave, and use the object's changedValues() to see if the name changed.

The right way to do it-- as far as Core Data is concerned, anyway-- is to override the setter for name. Unfortunately you need to turn off automatic code generation for Core Data to do that. I described the process in this answer: Using property observers on NSManaged vars

Upvotes: 1

Related Questions