/ ios

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 a UIViewController, we need to inject it into our factory Alert 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.

Writing easy-to-read and reusable UIAlertController code
Share this

Subscribe to David Yang