David Yang
Tips and posts for iOS developers from an iOS developer.
Writing easy-to-read and reusable UIAlertController code
How would you write some code to display a simple alert style UIAlertController?
The usual way
This is how we all learned it. From a UIViewController
:
let alertController = UIAlertController(title: "Hello", message: "My name is David Yang.", preferredStyle: .alert)
// making a "ok" acton button
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
print("ok button pressed")
}
alertController.addAction(okAction)
// present the alert
present(alertController, animated: true, completion: nil)
Now when running this code, you should get a native alert with a title, a message and a “OK” button that will print a message in the console when pressed.
Why do it differently different?
For a simple reason: we usually want to display this kind of simple alerts from many places in our app. Writing this code can be very repititive.
The idea here is code factoring: making this code reusable, easy to use and readable. Therefore, it will also become more testable and if tested, improve our code coverage and quality.
While experiencing with it, here is one way I came up with, trying to solve it.
Making it reusable
Let’s start.
struct Alert {
static func present(title: String?, message: String, from controller: UIViewController) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
// making a "ok" acton button
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
print("ok button pressed")
}
alertController.addAction(okAction)
// present the alert
controller.present(alertController, animated: true, completion: nil)
}
}
We just moved all the code to a static function in an Alert
struct. Now, presenting the alert from a UIViewController
will result in the following code.
Alert.present(title: "Hello", message: "My name is David Yang.", from: self)
Note: Since the
UIAlertController
needs to be presented from aUIViewController
, we need to inject it into our factoryAlert
method.
That’s a good start. But as you keep making your app evolve, you might end up wanting to make the action buttons customized (in terms of title, action handler and even number).
In my case, sometimes I want to be able to have an “OK” button. Other times a “Retry” button with a custom action handler. Or even a “Close” button without any action handler…
So let’s keep improving our solution.
Making its actions customizable
Let’s work with some Swift enums. We’ll take advantage of the power of assiciated values.
We’ll add an Action
enum in the scope of our Alert
struct.
extension Alert {
enum Action {
case ok(handler: (() -> Void)?)
case retry(handler: (() -> Void)?)
case close
}
}
Now we’ll just add things in it in order to make it easy to build a UIAlertAction
from one of our enum case.
extension Alert {
enum Action {
case ok(handler: (() -> Void)?)
case retry(handler: (() -> Void)?)
case close
// Returns the title of our action button
private var title: String {
switch self {
case .ok:
return "OK"
case .retry:
return "Retry"
case .close:
return "Close"
}
}
// Returns the completion handler of our button
private var handler: (() -> Void)? {
switch self {
case .ok(let handler):
return handler
case .retry(let handler):
return handler
case .close:
return nil
}
}
var alertAction: UIAlertAction {
return UIAlertAction(title: title, style: .default, handler: { _ in
if let handler = self.handler {
handler()
}
})
}
}
}
Finally, we’re all set to refactor our Alert.present(title:message:from:)
method in order to handle our list of Alert.Action
through a variadic parameter.
struct Alert {
static func present(title: String?, message: String, actions: Alert.Action..., from controller: UIViewController) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for action in actions {
alertController.addAction(action.alertAction)
}
controller.present(alertController, animated: true, completion: nil)
}
}
Now we can use our Alert
struct to present an alert with a custom title, message and a set of action buttons from any given view controller.
Finally…
Here is how presenting an alert will now look like.
Alert.present(
title: "Hello",
message: "My name is David Yang.",
actions: .ok(handler: {
print("ok button pressed")
}), .close,
from: self
)
With this code, we just:
- instanciated an alert style
UIAlertController
- set its title
- set its message
- set two actions to it:
- a “OK” action with a custom action handler printing a message
- a “Close” action without action handler
- present the created alert from the current view controller
Conclusion
Displaying UIAlertController
is a typical kind of code that can be easily made reusable through code factoring.
Swift also offers great tools to write readable code. Feel free to improve, customize or write other tools like this.