How to Bind My Array Controller to My Core Data Model

How do I bind my Array Controller to my core data model?

In a storyboard based project there is no (binding) reference from a view controller to the AppDelegate class.

A solution is to add a property and override init?(coder in the view controller

@objc let managedObjectContext: NSManagedObjectContext

required init?(coder: NSCoder) {
self.managedObjectContext = (NSApp.delegate as! AppDelegate).persistentContainer.viewContext
super.init(coder: coder)
}

then bind the ManagedObjectContext to ViewController -> managedObjectContext.

In the Attribute Inspector of the array controller don't forget to set the Mode to Entity Name, insert the entity name and check Prepares Content.

What is the new way of binding an NSArrayController to the managed object context of a Core Data document?

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.

What Happened to Core Data Bindings?

Control-drag:

Control-drag to the property, configure the binding and click on Connect.

Sample Image

Bindings Inspector:

Configure the binding and turn on the Bind to checkbox.

Sample Image

How do you bind a storyboard view to a Core Data entity when using NSDocument?

Steps to create a sample Xcode Document-Based Application project with Core Data, Storyboard, NSArrayController, NSTableView and Bindings.

Step 1 Create a Xcode project. Choose OS X Cocoa Application and select ‘Use Storyboards’, ‘Create Document-Based Application’ and ‘Use Core Data’.

Step 2 Select the data model. Add entity ‘Person’ and string attributes ‘name’ and ‘address’.

Step 3 Select Main.storyboard. Add a NSArrayController to the view controller scene. Set Mode to ‘Entity Name’ and set Entity Name to ‘Person’. Check ‘Prepares Content’. Bind Managed Object Context of the array controller to View Controller, Model Key Path representedObject.managedObjectContext.

Step 4 Go to the view of the view controller scene. Remove ‘Your document contents here’. Add a NSTableView. Bind Content to Array Controller, Controller Key arrangedObjects. Bind Selection Indexes to Array Controller, Controller Key selectionIndexes. Bind Sort Descriptors to Array Controller, Controller Key sortDescriptors.

Step 5 Bind Value of the text fields in the table view to Table Cell View, Model Key Path objectValue.name and objectValue.address. Check 'Conditionally Sets Editable'.

Step 6 Add two Push Buttons ‘Add’ and ‘Remove’ to the view of the view controller scene. Connect the actions to actions add: and remove: of the array controller.

Step 7 (Objective-C) Select Document.h. In method makeWindowControllers, replace statement [self addWindowController:… by

NSWindowController *aWindowController = [[NSStoryboard storyboardWithName:@"Main" bundle:nil] instantiateControllerWithIdentifier:@"Document Window Controller"];
[self addWindowController:aWindowController];
aWindowController.contentViewController.representedObject = aWindowController.document;

Step 7 (Swift) Select Document.swift. In method makeWindowControllers, at the end after self.addWindowController(windowController) add

 windowController.contentViewController!.representedObject = windowController.document

Step 8 Build, Run, Test.

ArrayController's CoreData selection binding not refreshed across multiple NIB files

It seems you're missing the fact that, when you create the second array controller on the Details.xib it has no relation to the array controller on the MainMenu.xib. They are two separate instances.

When you change the selection on the PopUp the only array controller affected is the one on MainMenu.xib.

You have several options here:

  1. When you create your DetailViewController pass a reference to the array controller on the Controller and bind to that (don't create a new one on the details.xib)
  2. Just use simple KVO to observe the selection on Controller, and programatically change your label value.
  3. Just use simple KVO to observe the selection on Controller and update the array controller on the DetailsViewController to keep them in sync.
  4. your solution here...

As long as you understand what's going on I'm sure you'll find the best solution to your original problem.

Problems with cocoa bindings w/ master-detail where the detail contains an array

The content set of the Chapters array controller is bound to the selection of the Books array controller. When you select a row in the Books table view, the Books array controller's selection has to be synchronized by binding the selections indexes. I usually bind Content to arrangedObjects, Selection Indexes to selectionIndexes and Sort Descriptors to sortDescriptors.

The bindings from the Chapters table view are the same as the Books table view. Bind content of the Chapters table view to arranged objects of the Chapters array controller.

Using an NSArrayController in Multiple Storyboard Scenes

The key to making this work is to have a NSArrayController instance in each of your descending NSViewController subclasses and binding them together through a central data source (most likely your NSDocument subclass). You can then set this data source as your NSViewController subclasses representedObject by passing it down through your descending
controllers. Here is an example of a storyboard application with an NSWindowController which has a content view controller that is a NSSplitViewController with two child view controllers (A Master / Detail setup):

class Document: NSDocument {

var dataSource: DataSource? = DataSource()

...
}

class DataSource: NSObject, NSCoding {

var items: [Item] = []
var selectionIndexes: NSIndexSet = NSIndexSet()

...
}

class WindowController: NSWindowController {

override var document: AnyObject? {
didSet {
if let document = self.document as? Document {
self.contentViewController?.representedObject = document
}
}
}

}

class SplitViewController: NSSplitViewController {

override var representedObject: AnyObject? {
didSet {
for viewController in self.childViewControllers as! [NSViewController] {
viewController.representedObject = representedObject
}
}
}
}

The trick is to bind the representedObject to each of your descending view controller's NSArrayController in the storyboard. You need to bind NOT ONLY the contentArray BUT ALSO the selectionIndexes.

The result is that the selectionIndexes on both descending NSArrayControllers are kept in sync because they are bound through the central data source (DataSource subclass in above example).

To make this all clearer I have created an example project that demonstrates this here: https://github.com/acwright/StoryboardBindingsExample

Using Core Data and Cocoa Bindings in multiple storyboard scenes

Instead of adding array controllers to reconstruct the selected list or player it's more straightforward to transfer the selected list and player to the other view controller. It requires some code to change the selected list and player when the selection in the table view changes.

class Controller: NSObject {
@objc dynamic var moc = …
@objc dynamic weak var selectedList : List?
@objc dynamic weak var selectedPlayer : Player?
}

Left-most List view controller:

The view controller is the delegate of the table view.

class ListViewController: NSViewController, NSTableViewDelegate {

@IBOutlet var arrayController: NSArrayController!

func tableViewSelectionDidChange(_ notification: Notification) {
let controller = representedObject as! Controller
if arrayController.selectedObjects.count == 1,
let list = arrayController.selectedObjects[0] as? List {
controller.selectedList = list
}
else {
controller.selectedList = nil
controller.selectedPlayer = nil // tableViewSelectionDidChange isn't called on the Player table view
}
}

}

Middle Player view controller:

The view controller is the delegate of the table view.

class PlayerViewController: NSViewController, NSTableViewDelegate {

@IBOutlet var arrayController: NSArrayController!

func tableViewSelectionDidChange(_ notification: Notification) {
let controller = representedObject as! Controller
if arrayController.selectedObjects.count == 1,
let player = arrayController.selectedObjects[0] as? Player {
controller.selectedPlayer = player
}
else {
controller.selectedPlayer = nil
}
}

}

Bind the Content Set of the Player array controller to View Controller.representedObject.selectedList.players

Right-most Detail view controller:

Bind the Value of the text field to View Controller.representedObject.selectedPlayer.name



Related Topics



Leave a reply



Submit