Using Action Closures with UIKit Controls

Updated on October 16th, 2021

⏱ Reading Time: 5 mins

When talking about user interface implementation on iOS, then SwiftUI is the framework that comes in everyone’s mind nowadays. SwiftUI gains more developers day by day, and gets improved with new features and possibilities year by year. However, this does not mean that UIKit framework belongs to the past yet; on the contrary, UIKit is, and will keep being around for a big part of the foreseeable future.

So, in every WWDC, besides all the news about SwiftUI that make us all get excited, there are also announcements and improvements that regard UIKit. And such a UIKit new feature is what this post deals with today.

Originally presented in WWDC 2020 along with iOS 14, UIKit makes it possible to just avoid using our familiar for years target-action pattern; the one that requires to specify a selector method to be invoked when a user interacts with a UIKit control. Instead, we may use closures, and therefore keep a control’s action together with its initialization and configuration.

The following few examples will make totally clear what exactly I am talking about. And if you have not been aware of this technique, then I bet that you are going to find it pretty interesting after having read this post.

The target-action pattern

When implementing user interfaces with UIKit, there are two paths we can take; either the graphical one using storyboards and XIB files, or to make things programmatically. When choosing the former approach, then it’s necessary to implement and connect special action methods to controls that provide user interaction. These methods are marked with the @IBAction keyword, and they are called when their matching controls trigger certain events.

Quite similar is the scenario that takes place when building a user interface programmatically. In order to start talking with some code examples, see the following few lines that initialize and configure a UIButton:

The interesting line here is the one right after the button’s initialization. What it says is simple; to consider the current instance of the class (UIView, UIViewController) as the place to look for the button’s action (target). The actual action is the buttonAction() method. It is invoked when the touchUpInside even takes place, meaning when the user lifts the finger of the button.

That’s not enough, however. In order to really make buttonAction() an action method, also called a selector method, it’s necessary to mark it with the @objc keyword:

This notation is inherited in Swift from the old good Objective-C, as well as the entire target-action pattern that was just presented. The real downside here is the need to define an additional method in order to implement the button’s action. As a natural subsequence, the more the controls, the more the selector methods we have to implement. Unavoidably, that can cause readability and maintainability issues, especially in large codebases.

Using closures instead of selector methods

Thankfully, since iOS 14 things can get better and simpler. It’s possible to follow an alternative route, and use closures instead of selector methods. You will soon realize that this is mostly convenient when the implemented action requires only a few lines of code. But that’s not a strict rule; closures approach can be used both for small and large implementations.

Time to see the first example of all that. We’ll start by initializing a UIAction object like so:

Note: Does UIAction look familiar? If you have ever presented an action sheet with UIKit, then you have definitely met it.

In the above snippet we initialize a UIAction instance and we assign it to the action constant. The argument we provide it with is the closure that we want to be called when the button we’ll create next gets tapped.

The next step is to initialize a UIButton, passing as argument the above action:

Note: Providing a button type is optional. In case we omit it as it happens here, the .system button style is used by default.

The above is all we need in order to react to user’s interaction! No need to create an additional selector method, or to specify self as a target. Of course, additional configuration should take place in a real app in order to add the button to the view, to style it, and so on.

Right above I initialized the action with the closure and the button in two subsequent steps. That was intentional, and for presentational reasons. In reality, both can be, and to save space should be, combined in one single step. The following code demonstrates the initialization of a button with a custom type. The provided UIAction is initialized with the action closure inline:

Using closures with more UIKit controls

Most probably, button is going to be the UIKit control that you will be using with closures the most. However, closures can go with other controls as well, and we’ll see a couple of them here.

The first one is going to be the UISwitch. It’s a good example that allows to discover something new; how to provide the UIAction instance with the closure after the control has been initialized. In the next snippet see the addAction(_:for:) method; it accepts the action closure, as well as an additional value, the event that triggers the closure’s call:

Even though the above is enough for most occasions, there will exist times that you will need to access the actual control inside the closure. Referring to the above example, that would be the switch instance.

We can easily get a reference to the control; it’s the closure’s argument value, which so far I kept replacing with the underscore symbol in all previous examples. This value is the UIAction instance, which has a property named sender of type Any. We just have to cast it to the control’s type in order to use it.

The following example demonstrates all that:

In addition to all the above, UIKit controls have initializers that allow to provide the action closure along with another value. For instance, the switch right next is initialized with a frame that is given as the first argument:

More controls can be created and treated like the UISwitch that was demonstrated above. Right next you can see a similar example, but using a UITextField this time. The action closure is given through the addAction(_:for:) method, and the specified event is different:

Conclusion

Using action closures instead of the target-action pattern with UIKit controls seems really handy. However, what if we had multiple controls, and inside each action closure there would be 10-15 lines of code, and additional configuration code? The container method of all that would end up being pretty long, hard to read and hard to maintain. In cases like that, sticking to what we already know with the target-action pattern is probably a more preferable solution. But in the end, which approach you’ll eventually choose is totally up to you. Now you know about action closures in UIKit controls, so consider to make what you read here another tool in your tool belt, and put it in motion when it’s suitable. I hope you found this article interesting, thank you for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.