Re: Getting started with Cocoa Bindings in My Project?
Re: Getting started with Cocoa Bindings in My Project?
- Subject: Re: Getting started with Cocoa Bindings in My Project?
- From: Ken Thomases <email@hidden>
- Date: Sun, 06 Sep 2015 13:14:50 -0500
On Sep 6, 2015, at 10:59 AM, Alex Hall <email@hidden> wrote:
> Since I'm using arrays, albeit retrieved from a dictionary, I thought an NSArrayController would do the job. I'm not sure what to enter for the key path, though. My view controller has a reference to both my table and my data model object, so I thought I'd give it the current array. I tried making the key path dataModel.myDict[datamodel.currentArray], but Xcode informed me that "myDict[dataModel" is not a valid key. Why it pulled just that part of what I'd actually entered, I'm not sure.
Bindings is built on top of key-value coding (KVC) and key-value observing (KVO). The model key path that a binding is bound to has to be, of course, a key path. A key path is a series of keys separated by periods. So, "dataModel.myDict[datamodel.currentArray]" looks like a path consisting of three keys, "dataModel", "myDict[datamodel", and "currentArray]". Obviously, that's not right.
KVC can't do a two-stage lookup. You effectively wanted it to parse datamodel.currentArray as a key path and get is value and then use that value as a key in a second key path, dataModel.myDict.<key from the first stage>. It doesn't do that.
KVC can traverse dictionaries, basically by treating them like an object with properties where the keys of the dictionary as the names of the properties. It does not support the bracket syntax you tried to use and it does not support getting the key from a subexpression.
> I then made a computed property in my view controller, so I could give the array controller a single-level property name but still get at the desired array.
A computed property can work, but you need to do extra work to make it KVO-compliant. Basically, KVO needs to be told what the computed property is based on so that when those underlying properties change it can emit change notifications for the computed property, too.
The easiest approach is to provided a class method named keyPathsForValuesAffecting<NameOfComputedProperty> which returns a set of key paths for the input properties. In Objective-C, this might look like:
+ (NSSet*) keyPathsForValuesAffectingMyComputedProperty
{
return [NSSet setWithObjects:@"inputProperty1", "otherInputProperty", nil];
}
In Swift, I expect this needs to be "dynamic", too.
> That failed too, but with slightly different error messages. This time, I saw
> [Swift._SwiftDeferredNSArray persistentStoreCoordinator]: unrecognized selector sent to instance 0x60800003ddc0
Could your array controller be configured in Entity mode (for Core Data) rather than Object mode?
> * I'm using Swift. Will that be a problem? I made both the computed property in my view controller and the dictionary of lists in my model "dynamic", and I made the object class of which my array's contents are instances subclass NSObject. Is there anything else I should do or know specific to Swift?
I'm not an expert, but I don't think there's anything else you need to do.
> * MVC I get, but why are my binding choices for my NSArrayController only controllers themselves? That is, why do I need to hook up my controller to my model *through another controller*? Should I not just give the NSArrayController my model directly?
Array controllers are what Apple calls "mediating controllers". View, window, and app controllers are "coordinating controllers". Surely, it's natural for the model to be owned by a controller. I mean, something is holding a strong reference to it. Anyway, it's not a mediating controller's job to own the model, only to mediate between the view and another controller.
It is technically possible to make an array controller manage an array that it owns, but I don't see it as a good idea. Why does it bother you?
> * When binding my NSTableView to my array controller, what, exactly, do I use for keys for both the table as a whole and the table cell? Again, I only have one column, if that matters.
From what you wrote below, I guess you're using a view-based table view rather than a cell-based one. The way you set up bindings differs for the two cases.
For view-based tables, you bind the table view's Content binding to the array controller, controller key path "arrangedObjects". You would not typically specify a model key path here, but you could if the table was not representing the elements of that array itself but rather some sub-property of the elements. Usually, though, the table represents the elements and individual cell views select a property to show. You bind the table view's Selection Indexes to the array controller, controller key path "selectionIndexes", no model key path. You bind the table view's Sort Descriptors to the array controller, controller key path "sortDescriptors", no model key path.
The table view uses the elements it gets via the Content binding to set the objectValue for each cell view, if the cell view has a setter for an objectValue property. NSTableCellView does. So do NSControls such as NSButton and NSTextField.
For NSTableCellView, though, setting objectValue doesn't _directly_ accomplish much. That class doesn't do anything with its objectValue, it just holds a strong reference to it. So, you would typically bind its subviews to it, with a model key path going through objectValue. For example, if you have an NSTextField as a subview of your NSTableCellView, you would bind the text field's Value binding to the containing cell view, model key path "objectValue.<some property of the array element>".
For a cell-based table view, you don't bind the table view's bindings. Rather, you bind the columns' Value binding to the array controller, controller key path "arrangedObjects". The model key path would be whatever property of a given array element should be displayed in that column's cell.
> * When and if I get all this working, do i still need my data source/delegate?
The data source and the delegate are two different roles.
Bindings replace the central functionality of the data source: -numberOfRowsInTableView: and -tableView:objectValueForTableColumn:row:. However, the data source still has a role for copy/paste and drag-and-drop, if you support those.
Bindings does not replace as much of the delegate responsibility, but see below.
> It sounds like bindings replace that, but if so, where do my table cells get generated? Right now I use that function to set the textField.stringValue of the cell to the correct item in the current array; where would that happen in a bindings context?
I'm guessing you're talking about -tableView:viewForTableColumn:row:, which I guess also means you're using a view-based table.
Strictly speaking, it's never necessary to use that method to populate the cell views with values. It can be used for that purpose, but the -tableView:objectValueForTableColumn:row: can be sufficient depending on your cell views.
Anyway, you don't need this with bindings. The table view bindings take care of getting objectValues for the cell views and intra-cell-view bindings take care of distributing that to the subviews.
(You might still need a -tableView:viewForTableColumn:row: method if your cell view identifiers don't match the identifiers of their respective columns. If they do match and the method would amount to nothing more than returning the result of calling -makeViewWithIdentifier:owner: on the table view, you can eliminate the method entirely.)
> * Assuming for a moment that my key is correct, and I want to bind to the array currently in use by the model, how would I do that?
The computed property discussed earlier should be fine.
> A dictionary lookup returns an optional, so for the moment--just to get things going--I'm forcing it to unwrap. Ideally, though, I'd include some way of handling a nil. How do bindings work with optionals?
The same way they work with object pointers which may be nil in Objective-C, which is to say mostly fine. Some bindings have options that can control how they handle nil, but generally they do the sensible thing. If the array controller's Content Array binding is bound to a property that's nil, then the array controller will just have empty content. Likewise, a table view would just be empty. Etc.
> At the end of the day, I want what bindings promises: my model updates, and each time it does (a row changes, is added, or is removed) my table updates to reflect that change.
Note that bindings only promises that for KVO-compliant model updates. In particular, you can't just mutate a mutable array and hope to have those changes reflected automatically. Mutable arrays are not themselves KVO-compliant. Objects may be KVO-compliant for their properties. Objects are not KVO-compliant in and of themselves. Likewise, property values or backing storage are not KVO-compliant in and of themselves. KVO hooks into the object, monitoring calls to setters and mutating accessors for the specific properties being observed. So, all modifications of such properties must be performed via calls to such setters and mutating accessors on the object which has the property. Direct manipulations of the backing storage for a property are not visible to KVO.
In particular, for an indexed collection (which may or may not be represented using an NSArray or NSMutableArray), you must always modify it by either wholesale replacing it using its setter or mutate it using the indexed collection mutation accessors.
<https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/AccessorConventions.html#//apple_ref/doc/uid/20002174-SW4>
> I'm using reloadData(), but I thought bindings would be far more elegant, extensible, and simple.
Personally, I do generally find bindings to be the best way to connect my views to my model. But there are limitations to bindings and when you bump into them, feel free to switch back to other techniques.
Regards,
Ken
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden