iosswiftuitableviewcore-datatransformable

UITableView "cellForRowAt: indexPath" Occasionally Calling "init?(coder aDecoder: NSCoder)" on Core Data Attribute


I have a core data entity Person with a transformable attribute age of type Age.

final class Person: NSManagedObject {
    @NSManaged public fileprivate(set) var age: Age
}

Age adopts the NSCoding protocol and has two variables value and scale, but only the value is saved:

class Age: NSObject, NSCoding {

    @objc public var value: Double
    public var scale = 1.0

    override public var description: String {
        return "\(scale * value)"
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(value, forKey: #keyPath(value))
    }

    public convenience required init?(coder aDecoder: NSCoder) {
        self.init(value: aDecoder.decodeDouble(forKey: #keyPath(value)))
    }

    init(value: Double) {
        self.value = value
    }

}

I display the age of an instance of Person within a UITableViewCell. This instance (person) has an age value of 10.0, i.e. person.age.value = 10.0, such that when the scale is changed programatically to say scale = 2.0 via a UIStepper, the UITableViewCell displays 20.0 (i.e. scale * value).

However, I'm finding that if I increment the UIStepper enough times eventually the initialisation of the Age class for the Person is called during the tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method, which returns an instance of Person at a given IndexPath. This obviously causes the init?(coder aDecoder: NSCoder) method within the Age class to be called, which resets the value of the scale property to 1.

Why does this happen, please, and is there a way fixed this at all? I ideally want the value of the scale property to always remain what it is set to on the UIStepper.

Thanks for any help on this matter.

EDIT

A given person at an indexPath is obtained in the following way:

private var people: [Person] {
    return Array(database.people).sortedArray(using: Person.defaultSortDescriptors)
}

private func person(at indexPath: IndexPath) -> Person {
    return people[indexPath.item]
}

Solution

  • Your people property is a computed property, which means you get a new people array every time you access it by people[indexPath.item]. So you are initializing a new Person instance every time you call func person(at:), which I guess in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell.

    Test this by changing the stepper value, and then make the cell disappear from the screen and come back to the same cell. Then the age will have been reset.

    Just make your people array a stored property like this.

    private var people: [Person] = Array(database.people).sortedArray(using: Person.defaultSortDescriptors)