Re: Basics of Cocoa Bindings
Re: Basics of Cocoa Bindings
- Subject: Re: Basics of Cocoa Bindings
- From: Quincey Morris <email@hidden>
- Date: Fri, 11 Sep 2015 21:43:17 +0000
(ugh, reposted due to size moderation, again)
On Sep 11, 2015, at 13:40 , Alex Hall <email@hidden> wrote:
> What I've done so far is set things up, but not gotten the app to run. At first, I was getting some odd errors, but I eventually realized that populating my tweetsArray object in viewDidLoad was a bad idea, so now I do that in the property declaration:
> dynamic var tweetsArray:[Tweet]=[Tweet(text:"tweet 11"), Tweet(text:"tweet 12”)]
You’ve *still* got a conceptual problem, but this one is about KVC, not bindings. KVC deals with properties which are values identified by *name* (key) within classes. There are scalar (one-to-one) properties — simple values — and collection (one-to-many) properties — multiple values which are isolated by (say) a numeric index.
In theory, there can be an “array” property, but that would be a scalar property whose single value happened to be a NSArray. Given the other details of KVC’s machinery, trying to use such an array property is a nightmare you don’t want to be stuck in. What you want, and what you really have, is an indexed collection of tweets, and you need to stop thinking of the property as an array — even though the backing store may have type [Tweet] and the KVC machinery accesses the collection via the NSArray class-cluster API — but not necessarily actual arrays (sometimes it uses proxy objects that refer to actual arrays, but that’s another discussion). Indeed, you should call your property “tweets”, not “tweetsArray”.
The internal magic of KVC is such that it provides a standardized mechanism of translating between property names (for a given class) and methods (of the class) the produce the actual values. Such methods are called accessors, and the method (naming) requirements depend on the kind of properties.
For scalar properties, there are two accessor methods: a getter and a setter (xxx and setXxx:, for a property named “xxx”). For indexed collection properties, there are about 8 different accessor methods, but you usually implement only a minimum required subset (of 2 accessors, one for inserting and one for removing). All the rest of the accessors default to standardized behavior and/or translate themselves into your custom accessors.
> As to my controller and bindings:
>
> array controller:
> * content array: view controller.tweetsArray (which again, is dynamic); controller key is empty
Correct, though as I said, you’re doing nothing but creating confusion by calling this “tweetsArray” rather than “tweets”, in your data model.
> * Content Array For Multiple Selection: View Controller.tweetsArray (though I don't need this, as I've disable multiple selection in my table)
Don’t need. Leave it alone and let the binding provide default behavior.
> * Selection Indexes: controller to bind to is View Controller, but I don't know what to put for a key path here. This is a property of the array controller itself, not anything it needs to get anywhere else, so I don't quite know why this field is here. Unless it's observing itself?
Don’t need, probably. If your view controller wants to have it’s own “mirror” of the selection that the array controller maintains, you can give it a suitable property, bind it here, and it will reflect the selection through the magic of bindings. There’s actually nothing interesting going on here, it’s just a piece of array controller convenience functionality.
> * Managed Object Context: same as Selection Indexes. I'm not quite sure what to put for values here.
Don’t need. This is for Core Data, and absolutely nothing else.
> Table View:
> * Content: array controller.arrangedObjects
Correct.
> * Selection Indexes: Array Controller.selectionIndexes
> * Sort Descriptors: Array Controller.sortDescriptors
Don’t really need. IIRC table views establish these particular bindings by default, if you don’t. You only ever set these manually if you’re bindings targets are non-standard. But what you’ve done isn’t wrong.
> Cell text field: this one's odd, because I thought the controller was supposed to be TableCellViewController, but that's not in the popup list of available options.
> * Value: Array Controller.selection.displayText (recall that displayText is a string property of the Tweet objects of which tweetsArray is full)
Nope. A table cell is a placeholder, since it obviously has to be different at every row. If you bind subviews to a particular target, all rows will show the same thing, and that’s not what you want.
Generally, you bind a subview’s value to TableViewCell (which *should* be in the popup), and use a model key of “objectValue.something”. In this case, it sounds like it should be “objectValue.displayText”.
There’s a completely separate piece of table view machinery that ensures the “objectValue”, for each table cell, contains a reference to the correct tweet for its row. This can itself be a binding, a default table behavior, or it can be done in a delegate method.
BTW, if you look at the table view documentation to find out how to supply this piece, you’ll find a lack of information. It’ll tell you that binding a table view’s content is an “advanced” topic, and give you no further help than a comically inadequate example.
> Right now, I'm getting an error:
> [Bindings_Tests.ViewController count]: unrecognized selector sent to instance 0x6080000c3db0
>
> Clearly, I'm missing a key somewhere, because that should be [ViewController countOfTweetsArray]
Nope. It should be “count”. The bound object, whatever it was, asked for the “count” of what it expected to be an indexed collection property. KVC takes such a request, and translates it into an accessor internally, or resolves it into default behavior. The latter is the correct outcome in this case. If it used the accessor, the method *would have been* called ‘countOfTweetsArray’, but the client — the bound UI element — doesn’t know or care. It just wants the “count”.
The actual problem here is that you left out the *property* key. I don’t know which binding this was, but it looks like you chose the view controller as target, and omitted to specify “tweetsArray” as the model key. (Or, due to one of the above problems, the entire binding should have been to a different target.)
> , which I *did* implement, just in case it would help:
> private func countOfTweetsArray() -> Int {
> return tweetsArray.count
> }
>
> I've also added:
> private func objectInTweetsArrayAtIndex(index:Int) -> Tweet{
> return tweetsArray[index]
> }
Don’t bother with these. They *are* the correct accessors for read access to an indexed one-to-one property called “tweetsArray”, but due to a horrible long-standing defect in KVC, it’s not really practical to use them (in many cases), and they won’t be used in this case, and you don’t need them in this case, simply because you have a property called “tweetsArray” which does their duty.
_______________________________________________
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