On Sep 6, 2015, at 10:59 AM, Alex Hall <mehg...@icloud.com> 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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to