Often I see code like this:
And I really don’t like it. The part that I don’t like is the name of the action-method. More specifically, the
didTap-part. It might as well be
cancelButtonTapped. All of those feel wrong™ to me. There are two main reasons for that:
Target-Action is a Command Pattern, not a Delegate Pattern
Deep in Apple’s Developer Documentation, hidden in the “Guides and Sample Code”, you can find an article explaining the Model-View-Controller (MVC) pattern. 1 They explain that the original MVC-pattern is made up of the Composite, Strategy and Observer patterns and go on to explain that their version of MVC uses two additional patterns: the Mediator and the Command pattern. Target-Action is the Command Pattern part of Apple’s MVC.
In the Target-Action version of the Command pattern, the UI element (e.g. a button) is the invoker, the
action/message passed to/method called on the object is the command and the
target is the receiver. Now, a command should be something like
scrollToEndOfDocument. Those are all actions an
NSResponder understands and might (depending on the specific instance and other circumstances) react to.
reload among others.
UIResponder is a little smaller but still has the classics:
selectAll() and of course
toggleUnderline. What did you think how the overlay menu you get for a text-selection works?
So Apple definitely calls their actions like commands, which seems like a good example to follow. In contrast, calling your action
didTapXButton sounds like a delegate method. But we are not using a delegate pattern here. We don’t expect a call from some very specific source asking as for specific information like in a delegate pattern. Instead we get a command to interpret and execute. And this command may or may not be invoked. Or may be invoked multiple times. By different senders, we don’t even know who will be triggering the action.
We don’t know the sender at compile-time and the sender shouldn’t be relevant
An IBAction can be invoked from any kind of control2. Your
didTapSendButton could be triggered when the user hits the return key on the keyboard after entering text in the message field. In a macOS app, I might trigger the sending of the mail from a menu item. Or maybe I tapped on a button in the TouchBar and not the one in your window. Or maybe by a triple tap gesture recognizer. It doesn’t matter, the intention of the user is clear, they want to “send” whatever they were writing.
By adding the name of the UI element to the method name, you couple the controller to the view more closely than necessary. And should you ever need to know how the user invoked the action, e.g. because you want to track whether anyone really uses the TouchBar, you can still look at the
sender property and do some customization3. That’s what it’s for. But adding some customization for special cases and otherwise keeping your action-method generic makes it much more flexible to be triggered from UI elements you might not even have heard of when you initially wrote the code.
Except for some very specific commands, it shouldn’t matter from where you action is invoked.
lowercaseWord means “make the currently selected word lowercase”.
copy means store the currently selected data (text, image, whatever) in the clipboard.
selectAll means “select everything I can see here”, whatever that everything is. And
undo means “I changed my mind, please roll back.”
Name Your Actions Like Commands
So Apple names their methods in this way, it fits the pattern and it decouples our controllers from our views. What’s not to like?
Thus, we should name our actions like commands. Make it
sendMessage(sender:Any) instead of
And by the way, it doesn’t matter whether you actually use Interface Builder or not. As long as you assign targets and actions to UI elements your action-methods should follow this naming pattern. I would even recommend to add
@IBAction in front of them, as it’s a nice marker to indicate to other programmers that this will be invoked from UI elements and they might not have full control over when and how and should thus not assume anything more than is provided by the action name.
I highly recommend reading it, independent of this blog post. ↩
And should you ever want to make your action only be invokeable by buttons, still name it in a generic way but make the type of sender e.g.
UIButton. It doesn’t help when you specify the action programatically, but Interface Builder will respect your choice and only offer it to be triggered by elements that fit that type. Yet another great reason to use Interface Builder. ;-) ↩
Did you know that you can add more than one action to a
UIControl(sadly not to
NSControl, thanks to Mark Aufflick for pointing that out)? So instead of customizing the
sendaction and adding your tracking there, just make the TouchBar element invoke the normal
sendaction and add another action
TrackingControlleras target. They will both be invoked when the user taps the TouchBar item. This way your view controller neither needs to know about tracking nor about how many different ways there are to invoke
sendand you can simply configure the actions in Interface Builder. 🤯 ↩
macOS makes this much clearer.
NSDocumentand friends all have lots of great action-methods. This is why you can so often build pretty advanced macOS applications by just combining some instances of Cocoa classes in Interface Builder and configuring them. Sadly, UIDocument and it’s friends have forgotten most of that, but I think following this naming pattern still fits well with Cocoa Touch and has all of the advantages described above. ↩