I have a Cocoa app with a similar layout like Mail.app:
A source list on the left where the user can select one of multiple entries
A detail table view in the main content area showing a list based on the selected item on the left
I'd like the selection in the detail table view to be maintained separately for each master item.
Currently I have only one detail table view and one array controller. When the master selection changes, I update the contents binding for the detail array controller.
I think I may need multiple detail array controllers -- one for each entry in the master list.
Is there an easier way/recommended pattern to do this?
You need an array controller for each table. For each detail array controller, you need to bind the contents binding to the selectedObjects key of the master array controller.
So if you had three array controllers, master, detail1 and detail2, then the content binding of detail1 should be set to master using a key path of selectedObjects.
The content binding of detail2 should be set to detail1, also using a key path of selectedObjects.
You don't need to change the contents binding programmatically.
Related
This is a macOS app, using Core Data, no Documents, and no Storyboards. I'm trying to use NSArrayControllers & bindings to display my data.
The data model 2 entities:
Book (which has attributes like author, title, blurb), and
Chapters (which has attributes like title)
The two entities are connected with a one-to-many relationship:
Book.chapters <—>> Chapter.book.
The app can have multiple books, which it shows in a tableView. Simple enough. I have an Array Controller set up in XCode called "Books Array Controller". I set its Entity as "Book" and check the "prepares content".
It's a master-detail, where one of the properties on the detail is an array to be shown in its own table. And this is what's giving me problems.
I bind the Books Array Controller as follows:
1. The managedObjectContext is bound to Delegate's self.managedObjectContext.
2. The bookListTableView has its Table Content > content bound to "Books Array Controller" controller key = arrangedObjects.
3. And then for each column in the table, I select the textViewCell and bind its value to the "Table Cell View" model key path = objectValue.title, objectValue.author, etc.
4. I have a textView that the "blurb" (attributed string) of the selected book by binding Attributed String to "Books Array Controller" controller key = selection, model key path = blurb.
All of this works just fine.
But I'm having a lot of problems trying to figure out how to display the chapter list in another table. I've tried creating another Array Controller called "Chapters Controller". I set its Entity as "Chapter" and check the "prepares content".
I bind the Chapters Controller as follows:
1. The managedObjectContext is bound to Delegate's self.managedObjectContext. (just like for the other one)
2. The Controller Content > content set is bound to "Books Array Controller" controller key = selection, model key path = chapters.
3. I bind the table's content to the "Chapters Controller" controller key = selection, model key path = chapters.
4. And then for each column in the table, I select the textViewCell and bind its value to the "Table Cell View" model key path = objectValue.title, etc.
Nothing ever shows in that 2nd table, the one for the chapter list. Not even a bunch of "Table View Cell" things. It's empty. I've tried a bunch of variations I won't bore you with. There's clearly something I'm missing. Any help?
The app correctly makes its Book objects, and the Books appear to correctly make their Chapter objects.
———
Update: I've attempted to follow Willeke's advice and am still not doing it right.
Here are the bindings...
Books controller binding:
Chapters Controller binding:
Books TableView binding:
Chapters TableView binding:
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.
im a osx-dev noob that is trying to build an application with three table views that will show the content of a core data store entity. But each table view is filtered on the attribute "status" of the entity.
the problem occurs when i also want to show the selected entity in textfields. I'm using three different array controllers with different fetch predicates. But in a textfield i can only bind the value to one array controller.
should i ditch the bindings and do it all programaticly or is there a simple solution to this? :)
here is an screenshot so you can grasp my app description.
Keep bindings to populate the text fields if it satisfies what you want to do with this GUI. I'd add an NSObjectController to control the one entity those fields represent. If you want the user's changes to those fields persisted, bindings are still awesome.
But I think with three tables that might control what's displayed in the text fields, you're going to need to have some sort of non-binding glue code that determines which of the tables wins. You can probably do everything you want by implementing some of the NSTableViewDelegate protocol.
If the text fields should display the last entity that the user clicked in any of the tables, simply have each table call the same tableViewSelectionDidChange delegate function. All three tables could have the same delegate. You can then call setContent on the NSObjectController from that function.
You could also use similar glue code to prevent more than one selection in any of the three tables, by having the same delegate function deselect everything in the other tables either through the view or the controller. But that's up to you and needs consideration of whether you want multiple selection, etc.
I'm attempting to use Cocoa bindings to populate an NSTableView using Core Data entities. I've arrived at a point where I'm boggled by the behavior of my application. The array controller is in "Entity Name" mode and is using the my "Song" entity.
The table view I am attempting to load the data into has two columns: one for the song name and another for the artist name (the artist is another entity which has a relationship to a song). Through Interface Builder, I have been able to successfully bind the array controller to the table, and setup bindings for the two columns. However, for some reason, one of the columns' bindings is working perfectly (artist name) while the other is not populating at all (song name).
I have gone through each view in the Document Outline and ensured that both columns are configured in exactly the same manner. I've attached some screenshots below showing the binding setup in Interface Builder.
Binding on the NSTableColumn representing the Song Title
Binding on the NSTextField within the NSTableCellView (the one that is not working)
Binding on the NSTextField for Artist Name (the one that is working)
The final (non-functioning) result
Is there an obvious reason why this approach is not working? Any help would be greatly appreciated. I can also provide any additional information that may be needed to answer this question.
It appears you are using a "view" based NSTableView as opposed to the older "cell" based. Each have a different configuration for bindings. In a view based table view you have to bind the tableview content to the array controllers arrangedObjects (see below), you don't have to deal with the individual column bindings that is for cell based table views.
Your remaining bindings look fine.
Just a wild guess. Since you're speaking of the Song entity.
Can it be that you have to bind the song name to objectValue.song.title?
If 'Artist' isn't just an attribute of the 'Song' entity but an entity itself (which it must likely be if it has a relationship to 'Song') then I think you need to have a separate array controller connected to that column in order for it to work properly.
I have an Core Data-based iTunes-like app I'm building that has an NSTableView in the main window and allows the user to select items in the table view and perform a "get info", like in iTunes. Currently I'm working on the single-item get info window, which displays when the user selects a single item in the main window (which is in MainMenu.xib) and pressed Cmd-I. This brings up the single-item get info window (in SingleItemGetInfo.xib).
The table view in the main window is populated via bindings to an NSArrayController. If I put an NSArrayController into the SingleItemGetInfo.xib file, I understand that's going to be a different instance of NSArrayController.
So, what's the proper way to bind the fields in the single item get info window to the selection the user has made in the main window's table view?
MainMenu.xib's Array Controller bindings:
Managed Object Context: AppDelegate.self.managedObjectContext
MainMenu.xib's Table View bindings:
Content: Array Controller.arrangedObjects
Selection Indexes: Array Controller.selectionIndexes
Sort Descriptors: Array Controller.sortDescriptors
This will work mostly the same way it does when the master and detail views are in the same window. The primary difference is that you need to bind the selection of the array controller in the detail xib to the selection of the master array controller.
It depends on how you have your app organized as to how you accomplish this. If both views are run by the same object then you can just make the master array controller an outlet connected to the controller (#property IBOutlet NSArrayController *masterArrayController;). Then in the detail xib you can bind the selection of the detail array controller to the selection of the master array controller.
If each view has its own controller you might also consider just passing the managed object from the master view to the detail view. Then you can bind to each of the attributes of the managed object in your xib file (i.e. bind to File's Owner self.detailObject.fullName). If you pass a reference I think all of the changes will go right back into the managed object context.
I'm working on a application with the this interface (sorry the language is Dutch):
http://www.flickr.com/photos/pluueer/5756159100/
The Add function (incl. the four NSTextFields) under the NSTableView are to moved to a sheet someday, but for now this is fine. I've set up bindings according to a tutorial (http://cocoadevcentral.com/articles/000080.php), but the tutorial doesn't supply how to add rows in the way I want to (just adds an empty row which you need to edit in the NSTableView).
I've got a connection between the 'Voeg toe' (Dutch for 'Add') button and the Array Controller. But after clicking I get the message:
2011-05-28 23:37:56.149 Hop Calc[4345:a0f] -[__NSPlaceholderDictionary initWithObjects:forKeys:]: number of objects (0) not equal to number of keys (4)
It makes sense, because I've not implemented anything for adding rows, but I just don't know how.
"Add a row to the table" is the wrong way to think of it. Your table represents a collection and a controller provides the information to the table, mediating between the table (view) and the collection (model). Since you mentioned bindings, the collection is likely managed by an NSArrayController. So you want to add a new object (of the kind your array controller manages) to the array controller's content array.
Simplest way: Connect the Add button to the -add: action of the NSArrayController. It'll add an empty row.
If you want more control, connect the Add button to your own custom action in some controller. That action will create an instance of whatever's represented by your array controller, prepopulate it (or whatever you want to do), then, using an outlet it holds to your NSArrayController, will call the array controller's -addObject: method to add the object (the possibly a -rearrangeObjects call to get the array controller to re-sort its contents).