-->

What is the new way of binding an NSArrayControlle

2019-03-09 12:49发布

问题:

Before Xcode went and added Storyboards for OS X apps you could connect an array controller to your document's managed object context by binding the Managed Object Context of the array controller to File's Owner with a Model Key Path of managedObjectContext. With storyboards there is no more File's Owner so where do you get the context from now?

Apple's documentation is behind in this area and there aren't any obvious places to bind to in Xcode. Obviously I can just fall back to a non-storyboard route and use the old method, but there must be a new way of doing it.

回答1:

So I have the answer from Apple. This is for Document based Core Data apps, the code is all in Swift but the idea is the same in Objective-C you just have to translate it.

The first answer they gave me was to bind the array controller to the view controller running the view with a model key path of self.view.window.windowController.document.managedObjectContex. The sample I was shown used this method and had no error messages at all, it was however a single view controller inside the window controller with one array controller. My setup is a window to a tab view to the views with two array controllers in the one scene. I was still getting Cannot perform operation without a managed object context once each time a new document was opened or created. The second solution, that worked for me was to still bind the array controller to the view controller but with a model key path of self.representedObject.managedObjectContext and then to add to the end of the document class's makeWindowControllers() function:

override func makeWindowControllers() {
……
    let tabViewController = windowController.contentViewController as NSTabViewController
    for object in tabViewController.childViewControllers {
        let childViewController = object as NSViewController
        childViewController.representedObject = self
    }
}

This solved the issue for me. Hopefully there is enough info here to show up when others google this issue.



回答2:

Using the default Xcode generated project and including CoreData puts the managedObjectContext member on the AppDelegate. You can add the following code to your ViewController, then use managedObjectContext as the "Model Key Path" with binding to ViewController for your NSArrayController.

lazy var managedObjectContext: NSManagedObjectContext = { 
    return (NSApplication.sharedApplication().delegate
        as? AppDelegate)?.managedObjectContext }()!

This simply creates a member which redirects to where your actual MOC is stored. This is useful because the NSArrayController binding happens before viewDidLoad(), hence why an instance member will not suffice. Also, if you want to refactor to a singleton CoreDataManager class, you can just change where to redirect to. Additionally, you could add this as a class extension to enable all ViewControllers to access your MOC.

Objective-C version upon request:

@interface MyViewController ()

@property (nonatomic, readonly) NSMangedObjectContext* managedObjectContext;

@end

@implementation MyViewController

- (NSManagedObjectContext*)managedObjectContext
{
    return ((AppDelegate*)([NSApplication sharedApplication].delegate)).managedObjectContext;
}

...

@end


回答3:

You have always been able to bind through NSApplication with a keypath of delegate.managedObjectContext if the application delegate owns the core data stack. Otherwise you could pass pass the MOC through to each view controller with a MOC property on each one, which is strongly preferred by those who argue that the app delegate shouldn't be used to own singleton MOCs, and that there's further utility in being able to provide each VC a separate MOC.

I believe you could also create a MOC instance in the storyboard in IB. There's also always been a MOC object for nibs, at least. Though I haven't used that enough to know how it relates to a programmatic core data stacks. Probably better to just have a MOC property somewhere you can access in either the VC hierarchy or app delegate



回答4:

Updated:

@theMikeSwan, well, it almost works for me. Here is what I have:

OSX EL Capitan GM Xcode 7GM and Xcode 7.1 beta

A standard Coredata/Document application

Replaced MainViewController with TabViewController and added 2 ViewControllers to that.

Added in your code to put representedObject in all view controllers in the tabviewcontroller.

Tab one is a view controller with a table, and an array controller that is bound to an entity called Profiles and the tableview is bound to that controller with +/- etc

Tab two is a view with view controller with a table, and an array controller that is bound to an entity called Commands and the tableview is bound to that controller.

There is a one to many relationship between the Profiles and the Commands entities with the names profiles <->> commands.

Both tab's work as expected with no errors independently - meaning I can add and delete Profiles->name in the table in the first tab, and I can add and delete Commands->name in the table in the second tab.

Next I want to enforce the one to many relationship - meaning if I select a Profile in the table in tab 1, and then switch to tab two, I want to see only the commands related to the selected profile in that table. That does not work. All entered Commands are shown in all cases, I have tried filters predicates, fetch predicates, etc, with varying degrees of disaster.

I have tried everything I can think of, and a lot of hacks I would rather not mention -

At this point I have added a second arrayController to the second tab view and bound it to Profiles entity and with self.representedObject.managedObjectContext etc... I added a NSTextField on the second tab view and bound it to the just added profileArrayController -> selection -> name to see what the controller was thinking...

The Profile->name in the second tab never changes regardless of what I select in the first tab's table, it is always showing the same Profiles->name. The commands listed in the table in the second tab are not affected by any selection in the first table.

It "feels" like the MOC on the second tab is not the same as the MOC referenced by the first tab. But that is just a feeling. I am lost, any suggestions on how to do a one to many relationship across tabs on a multi-tab view controller setup like this?

thanks Frank

Edited to add:

BTW, I have on some of those tabs, like the command tab multiple tables configured in one to many relationships on the same tab that work correctly - for example I have a synonyms table with bindings to a synonym entity via an array controller which is a many side of a relation ship with the command entity. It works fine as long as the tables/arraycontrollers are on the same tab, but when on separate tabs it is no joy.