Search code examples
swiftxcodemacoscore-datacocoa-bindings

How to display two 1-m related CoreData entities in two NSTableViews using NSArray bindings


I have an entity 'person' and a 1-m related entity 'visit' defined in CoreData. Now I want to display and modify their values in related tables using NSArray controllers with COCOA bindings on MacOS

My project is for MacOS and I'm using Xcode with Swift. I have defined the data structure and Viewcontroller objects using the Xcode builder. The table for the 'person' is already working and I can add or delete persons. However I'm not able to find a way to bind the 'visit' table to the selected person. I tried several options, like connecting the content of the 'visit' array controller as recommended in Master-detail using bindings with TWO NSTableViews, but all ended up in compile time errors.

As I'm working with the visual Xcode builders, the is not much code for the moment. Here is what I did so far:

I have defined the two entity classes with the Xcode data model builder, then the two tables and array controllers with the storyboard interface builder. Then I bound the table views content to the respective array controllers and the columns to their own table view with the respective entity attribute. I also added + and - buttons to add some rows. So far everything worked fine. But the details ('visit') had no connection to any 'person'. Hence I deleted all entries in the 'visit' table and tried to bind the content of its array controller to the selected person. That is where I get stuck. Maybe I missed some initialization of the CoreData entities, but I have no clue how to proceed


Solution

  • I'm looking at my working macOS project which has two NSTableViews in Master-Detail.

    • The .xib has two array controllers, one for the master and detail entities. Let's call them MasterAC and DetailAC. The Master and Detail table columns are bound to these array controllers.
    • In the bindings for the DetailAC, the Content Array binding is bound to: MasterAC.selection.details, where details is the name of the master-to-detail relationship.
    • In the bindings for both MasterAC and DetailAC, the Managed Object Context binding in both are bound to the same managed object context.
    • In the array controllers, only these two bindings (Content Array and Managed Object Context) are bound.

    If that does not fix it, I can poke around some more. Cocoa Bindings with Core Data in macOS are beautiful once you get them working :))

    Appendix. If you've got an-ordered set

    If the master-to-detail to-many relationship is an unordered set, and you are using the old school method of defining an index attribute on your Detail entity, you can define a detailsOrdered attribute in your Master class like this:

    func detailsOrdered() -> [Any]? {
        return details().arraySorted(byKeyPath: "index")
    }
    

    and then bind to MasterAC.selection.detailsOrdered instead of MasterAC.selection.details.

    The above implementation requires the following extension of Set:

    extension Set<AnyHashable> {
        func arraySorted(byKeyPath keyPath: String?) -> [Any]? {
            let unorderedArray = Array(self)
            let sortDescriptor = NSSortDescriptor(key: keyPath, ascending: true)
            let orderedArray = (unorderedArray as NSArray).sortedArray(using: [sortDescriptor])
            return orderedArray
        }
    }