UIAlertController with Function Builders
I always found the
UIAlertController API too verbose. You first have to create an instance of
UIAlertController, then create multiple instances of
UIAlertAction and finally add the actions to the controller.
In this post, I will show you how we can leverage the new Swift 5.1 feature of Function Builders to create a simplified and highly readable API.
Let’s take this sample code:
As I said earlier, there is a lot to write and we can do better. We could improve the API with regular Swift patterns, but the goal here is to use function builders to create this SwiftUI like sample code:
This new feature introduced in Swift 5.1 is not fully implemented yet. Instead of the public
@functionBuilder annotation, you have to use the private
@_functionBuilder one. Though, you can find the details of the proposal here.
To better understand the purpose of this feature, this extract highlights the use cases of function builders:
This proposal does not aim to enable all kinds of embedded DSL in Swift. It is focused on one specific class of problem that can be significantly improved with the use of a DSL: creating lists and trees of (typically) heterogenous data from regular patterns. This is a common need across a variety of different domains, including generating structured data (e.g. XML or JSON), UI view hierarchies (notably including Apple’s new SwiftUI framework, which is obviously a strong motivator for the authors), and similar use cases.
The documentation is not official, but after some digging, here are the methods we can implement:
buildBlock(_ components: Component...) -> Componentrequired
buildIf(_ component: Component?) -> Componentoptional, previously named
buildEither(first: Component) -> Componentand
buildEither(second: Component) -> Component, optionals
buildFunction have no effect for the moment.
What we want to build in our case is a list of alert actions. An
Action is composed of a title, a style (
cancel) and a function, triggered when the user taps the alert button on screen.
Once we have an array of
Action we can build an alert controller with this factory method:
So far so good, but we didn’t bring anything new yet.
Let’s see how to retrieve this array of actions and create our function builder. We saw earlier that the only required method is
buildBlock, so we will start here.
buildBlock combines a list of components to a single one.
Note: If we think about views and SwiftUI, that makes sense. From multiple subviews (the components for the
buildBlock will create a superview that contains all the subviews.
In our case, the
Component type is an array of actions, so the
buildBlock method can be written as follows:
Now that we have our
ActionBuilder, let’s use it to retrieve an array of actions and pass it to the
Note the use of the
@ActionBuilder attribute, that will allow us to use our new DSL in the
That way we can build the following:
But… how is this supposed to be better ?!
I guess that’s not what you expected…
It’s really weird to see a list of arrays of actions. Why not a list of actions? That would make more sense to write.
The explanation here, as we saw earlier, is that our
Component is the type
buildBlock takes a list of components
[[Action]]. That’s why we have to write it like this.
An evolution, in future implementations of function builders, would be to use the
buildExpression method (not yet available):
buildExpression(_ expression: Expression) -> Componentis used to lift the results of expression-statements into the
Componentinternal currency type. It is only necessary if the DSL wants to either (1) distinguish
Componenttypes or (2) provide contextual type information for statement-expressions.
buildExpression was working, we could write:
But for now, we are stuck with our list of
[Action]. That’s not really an issue though, because we can extract this weird behavior into factories:
And our code now matches our initial goal !
We can now add multiple actions to the same alert. But that’s not very dynamic yet…
What if we want to add actions conditionally ? What if we want to add multiple actions at the same time ?
Let’s try to add a condition.
We hit a compiler error:
That’s because we only have implemented the
buildBlock method and we need to add
buildIf. The implementation is simple, either we have a list of actions or else we return an empty list.
buildIf implemented, everything compiles and runs, the
if statement is taken into account.
But that’s not enough yet, because if we try to have a
else condition in our code, we hit the same compiler error again…
To overcome this, we need to add two more functions:
buildEither(second:) used when there is a decision tree with optional sub blocks.
And that makes our code compiling again with
At the moment we have no way to loop an array of strings and create actions out of them.
If we try, we always hit the same compiler error.
One way to solve this problem is to create an helper function that generates a list of actions for each element of a sequence, and then aggregates all those lists of actions into a single one.
And finally, we can use it like so:
We saw how we could improve the
UIAlertController API with very little code. The difficulty with function builders is to find documentation and to understand the cryptic error messages. The feature is really limited at the moment and maybe it’s for the best, to avoid overly complicated DSLs that would not be understandable. But be patient, swift folks are working on it.
You can find a gist with all the code here.
If you are interested in the subject, here is a list of resources you can use to create your own function builders: