4

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]
}
ajrlewis
  • 2,968
  • 3
  • 33
  • 67
  • `This obviously causes the init?(coder aDecoder: NSCoder) method within the Age class to be called`: With what coder is Age being initialized? I don‘t see how transformable fits in here. Is it about persisting Age and if a cell is reloaded the scale is lost? Was the coder supposed to store/persist the Age every time the UIStepper changes and on reload/later supply the wanted value to the Age? – Fabian Aug 12 '18 at 17:29
  • How do you get the array of persons? Can you provide a source code of `cellForRowAtIndexPath:` method? The initialisation of the `age` property may be caused by turning your `Person` object into fault. Further access to the `age` property will restore it from the storage by `init?(coder aDecoder: NSCoder)`. – cocavo Aug 13 '18 at 15:49
  • @cocavo Thanks for your help. I have included how the `person` is selected from an set of `People` stored in a `Database` entity. – ajrlewis Aug 14 '18 at 09:06
  • As I understood the `people` array is generated every time you access it. This is why you are getting a newly created `Person` object within `cellForRowAtIndexPath:` method. Try to generate the `people` array only once and store it as a property of view controller. I guess it should help you. – cocavo Aug 14 '18 at 15:09

1 Answers1

5

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)
sj-r
  • 561
  • 2
  • 10