One of the core capabilities of Interface Builder, when used as the resource editor of a Cocoa application, is connecting UI widgets to code with just a bit of click and drag. The code that is connected to consists of outlets and actions.
Outlets are straightforward, just pointers to objects. The basic thing to remember, as with all Interface Builder connections, is that you shouldn’t try to refer to them in your init
methods; use awakeFromNib
instead.
Actions are methods, more specifically methods referred to by name (in the nib file) or selector (at runtime) instead of (as in C/C++) by pointer. To connect an action in Interface Builder, you control-drag from the UI widget that will send the action message to the object that will receive it, and then you choose the method in that receiver whose message will be sent.
The reason actions are more complicated than outlets is because actions are really part of a target/action pair. Interface Builder needs to remember more than just the message, it needs to remember the target to send that message to.
Until recently, to me at least, the Interface Builder UI seemed to be a little fuzzy on that distinction. In the Info window for an object, under the Connections pane, there was an Outlets column and an Actions column. Under the latest Interface Builder in Panther, what was called Actions is now referred to as “Target/Action”, which states the actual situation more explicitly.
Another thing to keep in mind is that the Cocoa framework has the action/target pair functionality built into Interface Builder solely for itself. You can’t make your own method/object pointer pair variables in a class and set them via the same Interface Builder UI that Cocoa uses for action/target; there’s just no way to tell Interface Builder to treat your variables that way.
With me so far? Good, because it’s about to get a little more complicated.
A while ago, I made a specialized outline view. When you double-clicked some of the cells, instead of getting the usual text editor, for certain columns and under certain circumstances, you would get more specialized behavior, such as a popup menu or a sheet with multi-line text editing capabilities.
The way to get this behavior is (a) turn off the regular editing behavior for the cell by having the delegate for the outline view return NO for the method outlineView:shouldEditTableColumn:item:
and (b) send the message setDoubleAction:
to the outline view with, as its parameter, the selector to the method you want to use to invoke the custom editor.
Those of you who know how this works already will protest, “Hey, you missed a step!” And indeed I did. But the above steps work under special circumstances, and they work because of how target/action pairs work.
The control-drag that I mentioned for actions at the top of this post fills in two variables in NSControl
and its subclasses. It fills in the target
variable with a pointer to the object. And it fills in the action
variable with the selector to the method. Now, doubleAction
is action
‘s cousin in NSTableView
. It works exactly the same way. But you can’t control-drag to connect it via Interface Builder, you need to set it via code. So, you would probably guess correctly that setDoubleAction:
only sets the method, not the target, and you might look around in vain for setDoubleTarget:
. Nope, doesn’t exist. Instead, it also uses target
!
Why would this work even when you haven’t set target
to anything? Well, if the target isn’t set, the Cocoa framework tries to make a guess what the target should be. You can see the order in which it checks here. This order doesn’t mention the delegate for a control, but NSTableView may be a special case since not all controls have delegates. In any case, it’s a good idea to send a setTarget:
message explicitly.
There you have it. As with the solution to most pitfalls, esp. in Cocoa, the solution is very simple, but might not be easy to come up with on your own. Enjoy!