Thanks for the help, both you and Charles. I want to try to summarize the
procedure as I now understand it, to make sure I have it all straight. No sense
going and doing a bunch of work if I'm still missing something vital or don't
have it right.
My first step is to make a KVO-compliant backing store, because Swift arrays
won't do the job. As you said, my goal of updating an array and having my table
automagically update itself to match the array just won't happen. What I need
is a class that implements the methods described in the link you provided,so
that bindings will work correctly. Obviously I can use an array internally, but
the interface of the class *must* offer the necessary methods. I'll call this
new class MyKVOCClass, because I'm terrible at naming things.
Next, remember that my model is a dictionary of arrays right now. The model has
a single variable that can change to point at any of the arrays in the
dictionary; the table is to display only that selected array (the table is
view-based, one column, no multi-selecting, no editing). As the currently
selected array changes, the table should update. I'll change this to be a
dictionary of MyKVOCClass objects instead, so that when the array inside the
selected object updates, the bound controller knows about it and can take
action. Thus, instead of my model being [String:[MyObject]], it will be
[String:MyKVOCClass]. The class will handle subscripting like an array, plus
implement all the KVO stuff.
Next is the problem of accessing the current MyKVOCClass object from the view
controller, where my model is instantiated. I'll need a computed property for
this, which I'll call currentModelArray. This should return a reference to the
currently active array in my model. You both suggested I implement
private func keyPathsForValuesAffectingCurrentModelArray() -> Set<String> {}
in addition, so that as I change the value of the currently selected object in
my model, the controller is notified. At least, that's how I understand the
purpose of this method. The set this method returns contains strings; are those
all the possible values the computed property can ever hold, or are they
something else entirely? I'm just not clear on which strings to provide
here--what you referred to as "input properties".
Alright, we have a KVO-compliant storage system in my model, and we have a
properly configured computed property that will tell the controller (here
referring to the ArrayController) where in my model to look. Time to connect
things.
First, I'd look at the bindings inspector for the array controller. I'd set it
to use my view controller, because it likes to default to Shared User Defaults
Controller. Then I'd set the key to be the computed property I made earlier.
Sorry if my terms aren't quite right; I've been working on other aspects of
this project, so have removed the ArrayController from it for the moment and
haven't yet put it back in, because things break when I do that. :)
Next I'd look at the bindings for my table. I'd want to set the controller to
my ArrayController, I don't know the specific words I'd use for keys here, but
I'd *not* use any of my own variables, correct? This is all properties of the
controller itself. This lets the controller figure out things like how many
rows there are, what's selected, and so on.
Finally, repeat the previous step, but for the table cell. As before, I'm now
using all properties of the controller, nothing *I* made anywhere. This uses
the methods in my MyKVOCClass to get the actual data, then auto-generates the
cells as needed. I'm not sure how this would work for a cell that's more than a
text field, but that's a different topic, I guess. :)
That's how I understand the whole procedure. I thought it would be easier to
just say what I've got then to try to keep replying in-line. Thanks again for
the responses!
> On Sep 6, 2015, at 14:14, Ken Thomases <[email protected]> wrote:
>
> On Sep 6, 2015, at 10:59 AM, Alex Hall <[email protected]> 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
>
--
Have a great day,
Alex Hall
[email protected]
_______________________________________________
Cocoa-dev mailing list ([email protected])
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 [email protected]