tableview binding

Hi,


For OSX, bindings between a view-based tableView and an ArrayController can easily be made in IB: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TableView/PopulatingViewTablesWithBindings/PopulatingView-TablesWithBindings.html#//apple_ref/doc/uid/10000026i-CH13-SW4.

1-Bind the tableView to the ArrayController

2-Bind the controls in the cell to the "Table Cell View"


But how are we supposed to do it programmatically?

The binding between the TableView and the arrayController is fine in code, by putting in viewDidLoad something like (in Swift):

tableView.bind(NSContentBinding, to: dataController, withKeyPath: "arrangedObjects", options: nil)
tableView.bind(NSSelectionIndexesBinding, to: dataController, withKeyPath:"selectionIndexes", options: nil)
tableView.bind(NSSortDescriptorsBinding, to: dataController, withKeyPath: "sortDescriptors", options: nil)


But how are made the second part of the binding? This second part is much more blurry if undocumented. The documentation lists the classes that support Cocoa bindings (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CocoaBindingsRef/CocoaBindingsRef.html) but Table Cell Views are not mentionned.

Moreover, if it exists, where is supposed to be written the code ?

tableCellView.imageView?.bind(NSValueBinding, to: tableCellView, withKeyPath: "objectValue.something", options: nil])

This code assumes then that the tableCellView should be an IBOutlet.


Does anyone have a clue about these things?

Or am I completely off the mark?


Many thanks for your help !

Replies

The question is, where does your tableCellView come from? It's provided by (or indirectly by) your tableView(_:viewFor:row:) delegate method, but it can be obtained by creation or reuse, depending on the mechanism you use. If obtained by creation, it can come from instantiating a NIB file or created programatically,


If you're using a NIB to define the cell view, then the binding will already in the NIB file, so the view will get instantiated with the binding in place.


If you're creating a custom cell view, then you can bind things however you want. Things are only bound through "objectValue" in the normal case because that's the object that exists per row. The standard mechanism re-uses cell views for different rows at different times, so binding to the cell view wouldn't work.


Note that if you're creating custom cell views directly, then you're not going to get the benefit of the standard cell reuse mechanism. You'll have to cache them yourself, or not cache them at all.

Hi,



Thanks for your answer but it doesn't make it clearer to me.

To answer your question, I guess the tableView gets it's views by automatically invoking the method make(withIdentifier: owner:).

About the tableView(_:viewFor:row:) delegate method, I don't implement it, as Apple says that "when using Cocoa bindings, this method is optional if at least one identifier has been associated with the table view at design time", and "If this method isn’t implemented, the table will automatically call the NSTableView method make(withIdentifier:owner:)". And as recommended, I use the Automatic identifier in IB.


Here's what I'm trying to say (and sorry if I'm not clear enough, probably because of my bad english):

Let's say I have a view-based TableView (TV) in a view, stored in a nib File. Each column contains a tableCellView and each tableCellView contains different controls (for the example below, it's simply textFields). No bindings in IB at all, as I want them to be done in the viewController's implementation. So far, I've bound the arrayController (AC) and the tableView (TV):

func viewDidLoad() {
tableView.bind(NSContentBinding, to: dataController, withKeyPath: "arrangedObjects", options: nil)
...


With that done, I get the rows in the tableView: 3 objects in the AC gives 3 rows in the TV. But I have something like:

| Name | Age |

| Table View Cell | Table View Cell |

| Table View Cell | Table View Cell |

| Table View Cell | Table View Cell |



What I need to get now is the actual content for each row, something like:

| Name | Age |

| John | 34 |

| Alan | 12 |

| Peter | 56 |



So how (and where) am I suppose to code these bindings (Objectvalue.name, and Objectvalue.age)?



Josh

You're clear enough, it's just hard to explain anything to do with table views, plus there are so many possible configurations it takes a bit of discussion to figure it out.


I thought you might be doing everything programmatically, including creation of the table cell views without a NIB (or storyboard). If you have a table cell view defined in a NIB, then (as you say) if you do not implement tableView(_:viewFor:row:), the table view creates the table cell view from the information in the nib.


In addition, the table content binding by default sets the object bound to (per row) as the value of the table cell's "objectValue" property. So, bind the name field of the cell view prototype in IB to TableCellView with model key path "objectValue.name", where "name" is the property name of the property you want to display. (Or the path to the name, if the actual property is buried deeper in the data model hierarchy, something like "objectValue.group.family.person.firstName".)


In this scenario, there's no need for any programmatic binding of table cell view components — it's all done in IB.

Yes, sorry, I wasn't that clear in my first message.

What I meant by "doing it programmatically" is that I want to be able to set, in code, the bindings.


So I know it's all can be done in IB, but again, I need this to be done in code, because the table will be connected to different controllers...

Cocoa Bindings are based on Key Value Coding and Key Value Observing.


Check the NSKeyValueCoding protocol, especially value(forKeyPath:) and setValue(_:forKeyPath:).


Also, check the Key-Value Observing Programming Guide.

By avoiding the tableView(_:viewFor:row:) delegate method, you avoided the place where you could intervene to bind the table cell elements (subviews) via the table cell view's "objectValue" property. The whole point of the default you-don't-have-to-write-a-line-of-code behavior is so that you can configure everything in IB.


>> I need this to be done in code, because the table will be connected to different controllers


I don't even know what that means. What kind of controllers are you talking about? NSViewController? NSArrayController?


If your table might be bound to different data models (e.g. through different array controllers) at different times, so you can't bind (say) text fields because the key path is different for the different data models, then you might want to do the binding in tableView(_:viewFor:row:). However, there are better solutions to that problem.