Re: totally confused by bindings settings
Re: totally confused by bindings settings
- Subject: Re: totally confused by bindings settings
- From: Ken Thomases <email@hidden>
- Date: Thu, 5 Jun 2008 15:12:42 -0500
On Jun 5, 2008, at 10:47 AM, Daniel Child wrote:
First off, thanks very much for the lengthy response.
You're welcome. Let's see if we can resolve some of the remaining
confusion. :)
Hmm. An NSArrayController doesn't manage tables, it manages an
array (as its name implies). So, do you have an array somewhere
with instances of the Word class? Where is it? For example, is
it a property of File's Owner, with a key "words"?
The class structures are:
Word has three ivars {characters, reading, english} (all strings)
WordList has (for now) just one ivar {wordList} (a mutable array)
In practice, wordList will be an array of Word objects, but it is
simply declared as NSMutableArray.
This is simply a reduction to basics from part of a larger target
application where I think bindings will be very useful.
All OK.
Contains how? What is the interface of this WordList class?
What properties does it have?
wordList is an array of Word objects but is simply declared as
NSMutableArray. It could have other classes (and in the real
application would) but they're not needed for trying to get the
table to work.
OK. Just to rephrase this to match the terminology in my question,
that means that the WordList class has a property named "wordList"
which is a to-many relationship to instances of the Word class.
That doesn't seem right. The Class Name should indicate what
kind of elements are in the array being managed. That's still
Word, unless I'm misunderstanding what you're trying to achieve.
OK, thanks for the clarification. But binding to "Word" is not a
option given in Interface Builder.
At this point, we're not discussing establishing a binding. We're
configuring the NSArrayController so that it (and IB) knows what type
the elements of the array will be.
Similarly, the Content Array binding of the NSArrayController
should refer to the array of Words maintained by the WordList
instance (perhaps as a key path from the File's Owner, or
whatever). Therefore, it doesn't make sense to have
".arrangedObjects.wordList" appear in the key path of the table
column bindings. The arrangedObjects are already the words in the
WordList instance.
Let's suppose that File's Owner has a property "wordList" that is
a to-one relationship to a WordList instance.
I haven't altered File's Owner. It is simply NSApplication. I'm not
clear how I would even set a property for NSApplication. (If this
test were more complex and I were using a window controller for the
owner of a different file, I would totally see how to put in such a
property.)
You're right when you say (further down) that it's not common to
subclass NSApplication. If you did, though, you could tell IB about
the custom class using the Identity tab of the inspector (Command-6,
IIRC). In that case, your subclass could have properties of its own,
possibly including a WordList.
What about the more common case of not subclassing NSApplication? In
that case, your application-wide custom behaviors and data are
typically put in a custom class and an instance of that class is set
to be the delegate of NSApp. The easiest way to set this up is to
write up the class (the .h and .m files), drag an NSObject instance
into the nib, and set its class to your custom class. For purposes
of this discussion, let's say you name that object "MyAppDelegate".
Then, Control-drag from File's Owner to MyAppDelegate to connect the
application's delegate outlet to it.
In this case, you can establish the bindings directly to
MyAppDelegate. So, when I said "File's Owner" in my previous email,
substitute "MyAppDelegate".
You could also establish the bindings through File's
Owner.delegate.<whatever key path>. It amounts to the same thing for
this nib. In other nibs, the application delegate won't actually be
in that same nib, and so the ability to refer to it via a key path
from either the File's Owner or the Application stand-in will be useful.
Let's also suppose that the WordList class has a property "words"
which is the to-many relationship to some instances of Word.
Then, the NSArrayController's Content Array binding should be
bound to File's Owner with Model Key Path "wordList.words".
But in my case that means connecting to NSApp, and I don't see how
I would add properties to that. In fact, your suggestion of
supposing that File's Owner has a property "wordList" confused me
further and led me to read the entire thread posted recently on
that topic. I sympathize with the original author's confusion,
because as one participant pointed out, you don't generally "do"
much with File's Owner unless you're building a second nib. (I've
used it as a proxy for window controllers not so long ago, but
never did much with the FO of the MainMenu.nib.) I see that (like
someone else in the thread), I have also been mislead by some
beginner code that instantiates part of the model in the nib when,
as you point out, it's not generally a good idea. I was trying to
do this in my "stupid bindings" app listed above.
Your confusion is understandable. I'm glad you referred back to that
other thread, and you seem to have a good understanding of what
you've read.
Hopefully, what I said just above clarifies the point. In the main
nib it's true that you don't often change the class of File's Owner,
and so it doesn't have any custom outlets, although the delegate
outlet is useful.
Maybe if I talk my way through a description of my target "simple
app with bindings" it will become clear what I don't understand.
MODEL: Word and WordList classes, where WordList instances have an
array of words.
VIEW: Table that's going to display the one and only word list.
(Not document-based, and I'm only trying to learn binding basics,
so there will only be one word list.) Maybe some add and remove
buttons. Eventually a detail view, but not yet.
CONTROLLER: This is what I thought would be the canned (Cocoa-
provided) NSArrayController object to manage the array of words.
*** BUT *** I am also confused, as some tutorials / examples use a
separate (home-made) controller (e.g. MailApp demo) and others
don't. *** Since I'm not doing anything fancy (just Add and Remove
buttons) for now I don't see the need for a separate controller or
even a delegate.
In this case, you have both. There's a mediating controller, which
is the NSArrayController. There's also a coordinating controller
MyAppDelegate, and it will be the delegate. At the moment,
MyAppDelegate is little more than a container for your model. This
probably looks pretty close to the case where the model is
instantiated in the nib, which I warned against in the other thread.
Two points about that: instantiating the application delegate in the
main nib is a bit of a special case. In other nibs, the controller
often lives outside of the nib and is its owner. Second, it is
typical that a controller in MVC instantiates and holds the model,
but that isn't quite the same as it being the model.
Eventually, as this basic example is fleshed out into a more real-
world application, both the model and the controller will gain more
complexity. At that point, it will become clearer to you that the
controller is in the nib and has responsibilities for coordinating
between the model and the view. Similarly, the model will be more
complex and will more obviously _not_ be in the nib even if it is
instantiated and held by the controller which is.
FILE's OWNER: NSApp (proxy) since I only need MainMenu.nib => in
this case, FO is not a good place to put the wordList, since I've
read you shouldn't subclass NSApp.
NSApp will run (setting up Event loop and other good magical
things), and load the MainMenu.nib. Objects "freeze-dried" in the
nib will be unarchived, and any connections (a bunch of outlets I
don't usually use) may get handled automatically. This is where
things get fuzzy for me. By now I should have an instantiated
NSArrayController, an instantiated Window containing a View and
Table. And I somehow need to hook this controller to my model.
Thanks to the long File's Owner thread, I can visualize NSApp as
loading the nib and (sort of) being the nib's owner or nib's loader.
But since I can't place wordList in NSApp, how am I going to
connect to the WordList's ivar, wordList? I could create another
controller that contains in ivar of type WordList, and then
instantiate that in the nib. Are there better options?
Actually, I think you're doing pretty well. There are always other
ways to do things, but you've described almost exactly what is
normally done.
That is, this array controller is managing access to the words of
the wordList of File's Owner.
Not my case.... But I'm trying to understand this scenario as well.
FO: A window controller that has outlets, one of which will be the
table. This FO will also have an ivar "wordList". (Somehow) the
table columns get bound to values in the word objects kept in the
wordList. (The exact setup eludes me so far.)
In the scenario you're considering here, it would be:
*) NSArrayControler with "Class Name" set to Word. It's Content
Array binding would be bound to File's Owner with Model Key Path
"wordList.wordList". The first "wordList" is the property of the
custom window controller as you just described, which I'm assuming is
an instance of the WordList class. The second "wordList" references
the property of the WordList class which is the to-many relationship
to (array of) the Word objects.
*) A table column whose Value binding is bound to the
NSArrayController, Controller Key "arrangedObjects", Model Key Path
"characters". (Additional columns might use "reading" or "english"
for their model key paths.)
In the scenario using MyAppDelegate, the NSArrayController's Content
Array binding would be bound to MyAppDelegate but will otherwise be
the same. Likewise, the table column bindings will be the same.
[Later addition: I think you were assuming a DocumentApp, which I
haven't really messed with yet, but FO discussion helps me see how
that too might be a good case for placing wordList in there.]
Nope, I wasn't discussing a document-based application.
Now, the table columns would be bound to the NSArrayController,
with the Controller Key "arrangedObjects" and Model Key Path being
whichever property of an individual Word instance should be
presented in the column.
Given that wordList was simply declared as an NSMutableArray, how
will bindings know that wordList contains words?
They won't and they won't care. Key-value coding, observing, and
binding use strings to access object properties at run-time. The
dynamic nature of Objective-C allows this lookup-by-string and
doesn't require that the keys and key paths to be verifiable at build
time.
That said, when you told IB that the Class Name for the
NSArrayController was Word, you gave it sufficient information so
that it could help you out by presenting model key path options to
you. Note that the Model Key Path field in the bindings inspector is
fully editable. Just because it's prompting you with some likely
suggestions doesn't mean you're limited to those. You can type
whatever you like there. If at run-time the key path can be
resolved, it will be; otherwise, you'll get an error logged to the
console.
Note that the table column bindings are pretty much unchanged
from the case without the WordList class. This is a reflection of
the encapsulation provided by the MVC design pattern and KVC/KVO/
bindings. If the model changes -- in this case, the array of
words moving from being a direct property of File's Owner to being
a property of a WordList object held by the File's Owner -- the
controller which mediates between the view and the model may have
to changed, but the view itself need not.
I actually think your example is easier to understand than my set
up, since I can visualize a controller having an outlet (table) and
an ivar (wordList), with these being bound to each other via the
array controller. But I'm still going blank if File's Owner is NSApp.
I guess one thing is starting to be clear. You generally make two
sets of bindings.
1. Bind some view object to the controller.
2. Bind the controller to some model object.
For (1), my table columns need to bind to something. My guess? The
array controller. What "value" do I want returned by the the
controller? The values returned by the "arrangedObjects" key. Which
model key path? I'm not too sure. Logically, I'm thinking that
column 1 should have a model keypath of wordList.word.<ivar1>. But
I don't see how the controller even knows what wordList is, or
where? Sure enough, this approach leads to an error.
That leads me to think I need a custom AppController containing a
WordList ivar.
2008-06-05 11:17:15.986 Table Practice[265:10b] An uncaught
exception was raised
2008-06-05 11:17:15.987 Table Practice[265:10b] [<AppController
0x13f820> addObserver:<NSArrayController 0x137c30>
forKeyPath:@"words.<ivar1>" options:0x0 context:0x0] was sent to an
object that is not KVC-compliant for the "words" property.
2
I think what you're missing is this: the Model Key Path is relative
to the Controller Key which is relative to the bound-to object. (If
the bound-to object is not a NSController-derived object, then
Controller Key is not used.) So, when you tried to use
"wordList.word.<ivar1>", you were being redundant. Binding to the
arrangedObjects of the NSArrayController already got you to the words
in the wordList. The Model Key Path is then relative to a Word
instance, so it should just be the name of a property of a Word.
Oops.... This reminds me of something I read somewhere that certain
methods need to be implemented for KVC to work with arrays. Here's
their example:
Listing 6 Suppor ting mutableArrayValueForKey: for the to-many
transactions proper ty
- (void)insertObject:(Transaction *)transaction
inTransactionsAtIndex:(unsigned int)index {
// implementation specific code
return;
}
- (void)removeObjectFromTransactionsAtIndex:(unsigned int)index {
// implementation specific code
return;
}
But what exactly would the implementation specific code look like?
I'm guessing....
// insert
[[self wordList] insertObjectAtIndex: index];
// remove
[[self wordList] removeObjectAtIndex: index];
If so, I'm kind of surprised I need to add code. I thought I could
hook it all up in IB.....
That's not strictly necessary. From here <http://developer.apple.com/
documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/
Compliant.html> you can see that if -<key> returns an NSMutableArray,
you're good to go. The methods you're thinking of are useful if you
want to present a KVC-compliant interface for a to-many relationship,
but that property isn't implemented in terms of an NSMutableArray.
By the way, you got the implementation of the insertion a bit wrong.
You didn't pass the object to the array! It would be:
[[self wordList] insertObject:transaction atIndex:index];
More importantly, could someone clarify the rhyme and reason for
choosing when to:
- binding the content arrangedObjects versus
- binding the value (of table cells) to arrangedObjects.<ivar>
versus
- binding the contentArray to selection.<ivars?>
Apple's Figure 13 of Cocoa Bindings Programming Topics takes a
huge leap from the trivial case of two simple controls (slider
and textfield) to a maze of connections between and among columns
and controllers. I simply don't see when to choose among the
various options, or what they stand for.
In many cases, there's only one binding to make. For example, a
table column has a Value binding, but not a Content or Content
Array binding. For an NSArrayController, there are Content Array,
Content Array For Multiple Selection, Content Object, and Content
Set bindings, which can be confusing. You'll need to look in the
Cocoa Bindings Reference for the page on the NSArrayController
bindings. The most common case is to bind Content Array.
OK, maybe this leads to a key concept I'm trying to grasp. When are
you binding to values, and when to content?
Frankly, the terminology is fairly arbitrary. Or, at least, if
there's a rationale for the distinction, it eludes me.
However, you really never need to figure it out. Of the classes in
the frameworks which support bindings, they either use the "value(s)"
or "content*" terminology but not both.
You suggested I look at the reference for NSArrayController with
respect to contentArray. They say this:
contentArray
an indexed collection that specifies the content of the
NSArrayController.
The indexed collection is an NSArray instance or subclass, a
property that is accessible using the key-value-coding indexed
accessor methods, or is accessible through mutableArrayValueForKey:.
But I'm still a little fuzzy here. In my example, wordList is the
indexed collection.
Well, there's some confusion because you have a class named
WordList. We have, at times, considered an ivar on something (File's
Owner in one scenario, MyAppDelegate in another) called "wordList"
which is a reference to an instance of WordList. However, you have
also used the name "wordList" for the property of the WordList class
which is the actual to-many relationship.
So, MyAppDelegate.wordList is not an indexed collection -- it's a
WordList instance. On the other hand,
MyAppDelegate.wordList.wordList is an indexed collection.
In my earlier email, I hypothesized (since you hadn't yet said) that
the property name for the to-many relationship was "words". It's
something of a convention that a to-many relationship is a plural.
(See Apple's KVC documentation for examples of this convention.) In
that case, the NSArrayController's Content Array would be bound to
MyAppDelegate.wordList.words.
So I thought that should be it. But you said the object class
should be Word because wordList is an array of words.
The Class Name assigned to an NSArrayController indicates the type of
the _elements_ of the array being managed.
I'm obviously missing this key distinction (between value and
content), because to me either of the following sentences make sense:
a) I want my array controller to reflect (bind to) the values in
the table
b) I want my array controller to reflect (bind to) the contents of
the table
(all with the understanding that "reflecting" is a loose term to
mean a reciprocal and bidirectional communication so that changes
on one side are reflected in the other)
Sure. The terminology seems roughly interchangeable to me, too.
Don't worry about it.
Now, as to the question of what to bind to (arrangedObjects,
arrangedObjects.key, selection.key, etc.), that depends on what
you're trying to accomplish. If you want to access all of the
objects in the array being managed by an array controller, you
bind to that controller's "arrangedObjects" controller key.
In my case, I think the answer is yes. I want to display everything
in the wordList => use arrangedObjects (?).
If you want to access a particular property of the objects in
that array, you specify a model key path to that property.
In my case, the answer is again yes. I want to access specific
values of those objects => use the model key path. This will be
especially true once I'm ready to add the detail view. Again, this
is a point where it seems fuzzy. To me, since my model objects are
WordList and Word, my path should be for column 1 should be
wordList.word.<ivar1>.
Hopefully, my explanation above cleared this up.
But InterfaceBuilder is offering "self" as a starting point. I'm
not even sure who "self" is.
IB isn't necessarily all that smart. It has some keys that it knows
about, but it doesn't necessarily know which is best for any given
use. So, don't put too much stock in what IB "offer[s] as a starting
point".
The NSArrayController or my separate controller containing an
instance of WordList?
If you were to supply "self" as the Model Key Path, then it would be
relative to the bound object and the Controller Key Path. When those
two are NSArrayController and arrangedObjects, respectively, then
self would be applied to the objects of the array (the arranged
objects). So, you'd be binding to Word instances, themselves -- not
a property of the Word objects.
Good luck in following all that. :)
Cheers,
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