The Problem
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key active.'
Thanks to @GrahamPerks answer to this SO question, I inserted an exception breakpoint in my code, which now pauses execution at this line...
static var entityActive: Bool {
return entity.value(forKey: "active") as! Bool // <-- PAUSES AT THIS LINE
}
This obviously needs further explanation...
Background
I'm writing a Core Data app that uses a generic table view data source and delegate, more or less set up per Florian Kugler's book "Core Data", published in 2017 by objc.io.
I have successfully linked three separate UITableViewController
s to this generic data source / delegate. I am using a single main storyboard and these three controllers are linked to three UISplitViewController
master/detail views.
I have deleted the automatically generated dataSource and delegate connections within the storyboard UITableView
s (although whether I leave or remove these connections, seems to make no difference).
If I comment out the code for the static var entityActive
above, the project will successfully Build and Run.
My code uses the UITableViewDelegate
method tableView(_, willDisplay:, forRowAt:)
to change the .textColor
of text and .backgroundColor
of cells, based on an attribute "active", stored as a boolean Scalar Type value with each entity.
For clarity, I'm trying to get the "active" attribute (every entity in my data model has a common attribute "active") for each NSManagedObject
for the entity. The value of that "active" attribute (Bool
true
or false
) for each entity is then used to format the cell in the if...else statement in the following code.
To attempt to maximise code reuse and minimise code repetition, I have also placed this delegate method in my generic data source / delegate class.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let entityObjectActive = T.entityActive
if entityObjectActive == true {
cell.textLabel?.textColor = UIColor.black
cell.detailTextLabel?.textColor = UIColor.darkGray
cell.backgroundColor = UIColor.white
} else if entityObjectActive == false {
cell.textLabel?.textColor = UIColor.lightGray
cell.detailTextLabel?.textColor = UIColor.lightGray
cell.backgroundColor = UIColor.clear
}
}
The compiler doesn't complain.
I seem to be able to use the generic type T
(representing Core Data entities) to associate the static property entityActive
with my instance of entityObjectActive
- so this seems to work...
let entityObjectActive = T.entityActive
To confirm, I have the following:
generic data source class....
class MyDataSource<T: Managed,
Delegate: TableViewDataSourceDelegate>:
NSObject,
UITableViewDataSource,
UITableViewDelegate,
NSFetchedResultsControllerDelegate {
// lots of code...
}
protocol...
protocol Managed: class, NSFetchRequestResult {
static var entity: NSEntityDescription { get }
static var entityActive: Bool { get } }
}
and extension...
extension Managed where Self: NSManagedObject {
static var entity: NSEntityDescription { return entity() }
static var entityActive: Bool {
return entity.value(forKey: "active") as! Bool
}
}
Attempts at Problem Solving
I've done some reading attempting to resolve my problem, including a review of many blogs (on how to set up generics in general and generic table view data sources) and a lot of SO Q&A, in particular...
How to fix Error: this class is not key value coding-compliant for the key tableView.'
Uncaught exception: This class is not key value coding-compliant
setValue:forUndefinedKey: this class is not key value coding-compliant for the key
It seems that all the SO Q&A are directly related to an issue with IB connections within storyboards. Maybe this is my issue too - but if that is the case it seems to me to be very well hidden.
Any assistance or advice please?
PS: I'm learning Swift after being a long time coder in Obj-C and I'm really struggling with the Generics Protocols Extensions paradigm shift, so it could be that my use of generic types is incorrect?