Articles, podcasts and news about Swift development, by John Sundell.

Lightweight presenters in Swift

Published on 09 Sep 2018

Separation of concerns - the idea that each type should ideally have a very clearly defined area of responsibility - is one of the most universally recognized programming principles, but in practice, it can be easier said than done.

Especially when it comes to UI development, it can be really tricky to clearly separate each type from others, and it’s definitely not uncommon to see classes like views and view controllers end up with a large set of features and tasks - since they usually have to deal with many different things, ranging from layout, to styling, to responding to user input.

This week, let’s take a look at how we can use the presenter pattern to move some of those tasks - specifically related to the presentation of additional UIs - away from our view controllers and into separate, dedicated types.

Presentation logic

Some UI classes are very self-contained and don’t require a lot of effort to present, such as in this example - in which we’re pushing a simple ProfileViewController onto the navigation stack:

let vc = ProfileViewController(user: user)
navigationController?.pushViewController(vc, animated: true)

However, some types require a bit more setup before they can be used - which is not necessarily a bad thing - it can be a sign of just how powerful and customizable a type is.

One such type is UIAlertController, which was introduced in iOS 8 as a much more powerful - and also a bit more complex - replacement for the much simpler, and more limited, UIAlertView. Now, with UIAlertController, displaying a system alert requires adding a UIAlertAction for each of the alert's buttons and configuring closures to handle selection.

Let's take a look at how we can use the presentation pattern to encapsulate such presentation logic and setup. While there are design patterns in which presenters play a much more central role - such as in the MVP (Model View Presenter) pattern - in this case we'll use presenters in a much more lightweight way, and simply define them as types responsible for presenting a certain piece of UI.

Let's say we want to display an alert view to have the user confirm an action - for example deleting an item. Rather than having the presenting view controller itself configure the alert view, let's encapsulate all of that code in a ConfirmationPresenter, like this:

struct ConfirmationPresenter {
    /// The question we want the user to confirm
    let question: String
    /// The title of the button to accept the confirmation
    let acceptTitle: String
    /// The title of the button to reject the confirmation
    let rejectTitle: String
    /// A closure to be run when the user taps one of the
    /// alert's buttons. Outcome is an enum with two cases:
    /// .accepted and .rejected.
    let handler: (Outcome) -> Void

    func present(in viewController: UIViewController) {
        let alert = UIAlertController(
            title: nil,
            message: question,
            preferredStyle: .alert
        )

        let rejectAction = UIAlertAction(title: rejectTitle, style: .cancel) { _ in
            self.handler(.rejected)
        }

        let acceptAction = UIAlertAction(title: acceptTitle, style: .default) { _ in
            self.handler(.accepted)
        }

        alert.addAction(rejectAction)
        alert.addAction(acceptAction)

        viewController.present(alert, animated: true)
    }
}

In this case we use a struct to define our presenter - mainly since it doesn't have to manage any state, and since it also enables us to take advantage of Swift's auto-generated struct initializers. With the above in place, we can now super easily present a confirmation alert in any view controller, like this:

class NoteViewController: UIViewController {
    func handleDeleteButtonTap() {
        let presenter = ConfirmationPresenter(
            question: "Are you sure you want to delete this note?",
            acceptTitle: "Yes, delete it!",
            rejectTitle: "Cancel",
            handler: { [unowned self] outcome in
                switch outcome {
                case .accepted:
                    self.noteCollection.delete(self.note)
                case .rejected:
                    break
                }
            }
        )

        presenter.present(in: self)
    }
}

The beauty of the above approach is that it lets us easily reuse our presentation code without having to either subclass UIAlertController, or use something like an extension on UIViewController - which would add the capability to display a confirmation alert to all view controllers, even those that really don't need it.

We can now simply create a presenter and call present() on it whenever we want to display a confirmation alert 👍.

Wrapping things up

Presenters can also provide a great way to ensure that certain view controllers get presented in a correct way. When doing modal presentation, some view controllers need to be wrapped in a UINavigationController, in order to support further navigation within that new modal stack, and that can be easy to forget if the same view controller is presented in multiple places.

Let's take a look at another example, in which we're using a MovieListViewController to display a list of the user's favorite movies. Our app displays lists of movies in many different places, so we've made MovieListViewController very flexible in order to support all those use cases - by enabling a UITableViewDataSource to be injected depending on the context.

To contain all of that setup code, as well as ensuring that our MovieListViewController gets correctly wrapped in a navigation controller, let's again use a presenter - this time we'll call it FavoritesPresenter:

struct FavoritesPresenter {
    let manager: FavoritesManager

    func present(in viewController: UIViewController) {
        let dataSource = FavoritesDataSource(manager: manager)
        let list = MovieListViewController(dataSource: dataSource)
        let navigationController = UINavigationController(rootViewController: list)

        list.navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .done,
            target: navigationController,
            action: #selector(UIViewController.dismissWithAnimation)
        )

        viewController.present(navigationController, animated: true)
    }
}

The above UIViewController.dismissWithAnimation method is a simple wrapper around dismiss(animated:completion:) in order to be able to use it as an Objective-C selector.

Just like with our ConfirmationPresenter before, our FavoritesPresenter makes the call site very nice and clean - and lets us reduce the need to put this kind of presentation logic in any view controllers:

class HomeViewController: UIViewController {
    func presentFavorites() {
        let presenter = FavoritesPresenter(manager: favoritesManager)
        presenter.present(in: self)
    }
}

Another nice benefit of this approach in this case, is the fact that HomeViewController (and all other view controllers presenting favorites) don't have to be aware that the presented view controller gets wrapped in a UINavigationController - which gives us much greater flexibility going forward, in case we want to move to another navigation paradigm in the future.

Enqueued presentation

Let's take a look at a final example of how lightweight presenters can be really useful. In this case we want to present multiple small tutorials, that each teaches the user about a different aspect of our app. To enable multiple parts of our code base to present tutorials without having to worry about whether a tutorial is already being presented, we create a presenter to manage all of that state and logic.

Here's what such a presenter could look like. In this case we'll use a class, since we'll need to keep track of the currently presented view controller, as well as the queue of upcoming tutorial items:

class TutorialPresenter {
    private let presentingViewController: UIViewController
    private weak var currentViewController: UIViewController?
    private var queue = [TutorialItem]()

    init(viewController: UIViewController) {
        presentingViewController = viewController
    }

    func present(_ item: TutorialItem) {
        // If we're already presenting a tutorial item, we'll
        // add the next item to the queue and return.
        guard currentViewController == nil else {
            queue.append(item)
            return
        }

        let viewController = TutorialViewController(item: item)

        viewController.completionHandler = { [weak self] in
            self?.currentViewController = nil
            self?.presentNextItemIfNeeded()
        }

        presentingViewController.present(viewController, animated: true)
        currentViewController = viewController
    }

    private func presentNextItemIfNeeded() {
        guard !queue.isEmpty else {
            return
        }

        present(queue.removeFirst())
    }
}

We can now simply call tutorialPresenter.present() from anywhere where we want to present a tutorial, and our presenter will take care of the rest! 👍

Conclusion

The presenter pattern can be really powerful, even if it's used in a very lightweight way. By encapsulating all of our presentation logic into a dedicated type, we can end up with code that is easier to reuse, and since our presenters have such a narrow scope - they only manage the presentation of a view or a view controller - they're usually quite easy to maintain as well.

There are of course many other ways to use presenters in Swift, including fully adopting a design pattern such as Model View Presenter - but with a lightweight approach such as this one, we can take advantage of presenters without having to move all of our code to a new design pattern. On the other hand, we are kind of introducing a new abstraction or architectural concept into our app - so it's important to clearly define exactly what role we want something like presenters to play in our app in general, in order to avoid confusion.

What do you think? Have you used the presenter pattern like this before, or is it something you'll try out? Let me know - along with your questions, comments or feedback - on Twitter @johnsundell.

Thanks for reading! 🚀