Remind iOS: Present All the Things!

For the past couple years we’ve been implementing new features with an MVVM-driven approach. Over that time, we’ve settled into a couple of good (I think) patterns for implementing UIView subclasses, composing them in various view hierarchies, and handling the interactions between them. Ultimately, the goal of any iOS application is to get data presented onto the screen, so I thought I’d share what’s been working well for us in that department.

Layout In Code

It’s a debate where every team eventually has to pick a side: Do we use storyboards and xibs, or do we write all of our layout in code? To make a long story short, our team started with Interface Builder, but we abandoned xibs a few years ago and have gone to fully programmatic layout code using SnapKit as our DSL of choice. Every team’s needs will be different, but we think it’s important that we can effectively code-review each other’s layout implementations and potentially have multiple people working on the same screen, neither of which are really practical with xibs.

I’ll leave it at that — you do you.

Use UIView Subclasses Liberally

Nothing clutters the actual logic of a UIViewController like big blocks of view construction code. Whenever you have a logical grouping UI primitives (UILabel, UIImageView, UIButton, etc), make a UIView subclass that encapsulates all of the relative layout inside that view container. This allows you to build the view hierarchy for your view controller in simpler, larger, more logical chunks of UI.

One of the more complex UI layouts we have in our application is the settings screen for a class the user owns. Let’s look at the layout and focus on the CLASS SETTINGS section, which contains two items, each consisting of two labels and a switch.

On our first iteration, our view controller maintained property accessors for each of those primitives:

class ClassSettingsViewController: UIViewController {
    ...
    private let isPublicTitleLabel = UILabel()
    private let isPublicMessageLabel = UILabel()
    private let isPublicToggle = UISwitch()

    private let over13TitleLabel = UILabel()
    private let over13MessageLabel = UILabel()
    private let over13Toggle = UISwitch()
    ...
}

The redundancy here should be a smell. Each pair of primitives is going to be styled identically, so we’ll either have to repeat ourselves or extract the styling into a set of functions:

class ClassSettingsViewController: UIViewController {
    func viewDidLoad() {
        ...
        self.styleSettingsTitleLabel(self.isPublicTitleLabel)
        self.styleSettingsTitleLabel(self.over13TitleLabel)
        ...
    }

    private func styleSettingsTitleLabel(_ label: UILabel) { ... }
    private func styleSettingsMessageLabel(_ label: UILabel) { ... }
    private func styleSettingsToggle(_ toggle: UISwitch) { ... }
}

This is better, but we’re still cluttering our view controller with a bunch of pedantic layout and styling. The better move is to create a dedicated UIView subclass with these two labels and a switch and use two instances of it in our view controller.

class ClassSettingsViewController: UIViewController {
    ...
    private let isPublicView = SettingsToggleView()
    private let over13View = SettingsToggleView()
    ...
}

private class SettingsToggleView: UIView {
    private let titleLabel = UILabel()
    private let messageLabel = UILabel()
    private let toggle = UISwitch()
    ...
    func buildView() {
        // Layout construction + static styling
    }
}

This facilitates more code reuse and keeps the nitty gritty of more complex view layouts separated from the core logic of your view controller. An additional benefit to this approach is that when you subclass UIView, you’re able to override its intrinsicContentSize property (and if necessary invalidate it) to let the layout system do more of the work for you.

Give Every View a Presentation Interface

A general rule we’ve imposed on our UIView subclasses that all internal view primitives should be private. In the above example you’ll notice that we’ve declared titleLabel as private. If we had made it public, it would be fully exposed for the view controller to do whatever it wants with it. This might seem benign, but as a result, the view controller might assume that a simple call to self.isPublicView.titleLabel.text = "<A bunch of text>" is a reasonable thing to do. The problem with that is that the design calls for these labels to have a lineSpacing value of 3, which is only accomplished by setting the label’s attributedText. In general, leaving the view internals publicly exposed allows and encourages future programmers to do the wrong thing accidentally.

The solution here is to keep all of the internal view primitives private and expose a function that takes data and binds it to the view, applying all the desired styling internally.

class ClassSettingsViewController: UIViewController {
    func present(model: ClassSettingsViewModel) {
        ...
        self.isPublicView.present(title: "<toggle title text>", 
                                  message: "<toggle message text>", 
                                  isOn: model.isPublic)
        ...
    }
}

private class SettingsToggleView: UIView {
    private let titleLabel = UILabel()
    private let messageLabel = UILabel()
    private let toggle = UISwitch()

    private var titleAttriubtes: [NSAttributedString.Key : Any] = {...}
    private var messageAttriubtes: [NSAttributedString.Key : Any] = {...}
    
    func present(title: String, message: String, isOn: Bool) {
        self.titleLabel.attributedText = NSAttributedString(string: title, attributes: self.titleAttributes)
        self.messageLabel.attributedText = NSAttributedString(string: message, attributes: self.messageAttributes)
        self.toggle.isOn = isOn
    }
}

This is essentially just an extension/application of our MVVM philosophy — you can think of the parameters to func present(...) as the ViewModel for that view. In simple cases, that ViewModel is essentially a tuple represented by the parameters of the function, but as a view becomes more complex (for example, the top-level view for the view controller above), it becomes appropriate to bundle those parameters into a dedicated ViewModel struct. This approach is particularly effective when you have domain models that you’d like to directly bind to a view. You absolutely do not want your presentation layer to know or care about the domain models of your business logic, so using a ViewModel gives you a layer of separation that makes your overall architecture a lot more flexible.

Let’s look at this example of selecting recipients in our message composer. The teacher can select any combination of their classes or individual students, but both domain objects are presented the same way, so we simply create constructors for the view model that transform the domain objects into presentation objects.

Full disclosure: this example has been simplified quite a bit for brevity and clarity, but the pattern shown here is representative of our actual production code.

class ComposerRecipientCell: UITableViewCell {
    struct ViewModel {
        let target: Composition.Recipient
        let name: String
        let avatar: AvatarViewModel
        let info: String?
    }

    func present(model: ViewModel) {
        self.presentedRecipientTarget = model.target
        self.nameLabel.text = model.name
        self.infoLabel.text = model.info
        self.infoLabel.isHidden = model.info == nil
        self.avatarView.present(model.avatar)
    }
}

extension ComposerRecipientCell.ViewModel {
    init(remindClass: RemindClass) {
        self.target = .group(uuid: remindClass.uuid)
        self.name = group.name
        self.info = "\(group.membershipCount) people"
        ...
    }

    init(user: RelatedUser) {
        self.target = .individual(uuid: user.uuid)
        self.name = user.name
        self.info = user.role
        ...
    }
}

Since we’re using an intermediary ViewModel in our presentation interface above, it’s trivial to add a new kind of domain object as a valid recipient just by implementing a new constructor. The only real business logic we’re leaking into the presentation layer is that a Composition.Recipient enum type exists, which is a simple (type, uuid) pair used throughout our composer classes, so I can live with that. The general idea here is that defining a constrained presentation interface for your view provides effective separation of your presentation layer from your business logic and allows you to easily bind data from arbitrary sources to your custom view. By keeping the underlying view primitives private, you give a future developer less to think about when reusing a view component and make building modular view hierarchies easier.

Give Interactive Views a Data Interface

Getting back to our class settings example, now that our primitive internal views are private, our view controller has no way to access whether the toggle is enabled or not (which we have to read in order to post a change to that setting to our backend API). In this case, it’s simple to add an accessor for that value without exposing the UISwitch itself.

private class SettingsToggleView: UIView {
    private let toggle = UISwitch()

    var isOn: Bool {
        return self.toggle.isOn
    }
}

In most cases we expose these properties as read-only, since presenting new data is going through the present(...) function already. In cases where the interface for a view is only one piece of data, it’s appropriate to skip the present(...) function and simply expose a read-write property.

class TextHeaderView: UIView {
    private let label = UILabel()

    var text: String {
        get { return self.label.text ?? "" }
        set { self.label.text = newValue }
    }
}

This works well enough for situations where you simply need to query and set a value at some point in your code. A more interesting case is when your UIView subclass contains buttons that the application needs to react to immediately. Let’s look at this action bar we have pinned to the keyboard in our message composer:

Here we’re going to expose a closure combined with an enum describing the possible actions the user can take on this view.

class ComposeBarView: UIView {
    enum Action {
        case attach, schedule, submit
    }
    var actionHandler: ((Action) -> Void)?

    ...

    @objc private func pressedAttach(_ sender: UIButton) {
        self.actionHandler?(.attach)
    }
}

class CompositionViewController: UIViewController {
    ...
    func viewDidLoad() {
        ...
        self.composeBar.actionHandler = { [weak self] action in
            switch action {
                case .attach: ...
                case .schedule: ...
                case .submit: ...
            }
        }
    }
}

A fair amount has been written about using closures for delegation instead of the Objective-C pattern of a protocol and weak delegate property, but it bears repeating because I think it’s such a dramatic improvement over traditional delegation for this simple “report some action was taken” case. Combining a lightweight closure property with Swift’s excellent enums and their associated values gives all the functionality of traditional delegation in a smaller package and with better support from the compiler. (Especially if you dress it up a bit!)

Combining these data interfaces with a presentation interface makes your UIView subclasses feel very “functional” — they have a set of inputs and a set of outputs, both of which are clearly and explicitly defined. Things that are functional are easier to compose and combine in different ways, and this leads to well-defined view components that can be used throughout your application.

Explicitly Scope Your Views For [Non-]Reusability

The patterns discussed above were established gradually as we explored how we would implement the Design Language System that our design team has been working on for the next major iteration of the Remind app. The core idea is to build up a set of reusable component views that our engineering and design teams have agreed upon to build our UI faster — implementing a new screen is way easier when you’re combining existing components instead of building everything from scratch. But having a well-defined design language doesn’t mean there won’t be any one-off, feature-specific UI components. In order to maintain a clean design language while allowing per-screen flexibility, it’s important to clearly separate your reusable components from your one-off widget implementations.

The rule of thumb we’ve settled on for our codebase is that unless a view is an explicitly agreed upon reusable design component, it is defined as a private class within the file of the view controller that presents it. We saw this already in the earlier examples where SettingsToggleView was defined privately within the view controller’s ClassSettingsViewController.swift file, but the commonly used TextHeaderView was defined publicly (technically as internal by default) and would reside in its own TextHeaderView.swift file.

This is a small and simple rule that barely counts as a “pattern,” but it goes a long way in ensuring that you’re speaking the same language as your design team and are building UI with the correct assumptions and expectations.

Functional Views => Functional View Controllers

Probably the most written-about topic in iOS development is how to avoid the infamous “Massive View Controller” anti-pattern. There’s been a new surge of discussion around this topic since Dave DeLong’s excellent A Better MVC series started dropping in late 2017. That series really transformed how I think about using view controllers, and you should read the whole thing, but the most salient point for me came in Part 2:

In general, a view controller should manage either sequence or UI, but not both.

I could probably write another whole post specifically about view controllers (and I might), but for now I will leave it in general terms: the same way we’ve discussed views having clearly defined inputs and outputs, so too should your view controllers. Instead of responding to the selection of an item by creating and pushing a new view controller, simply post a result from that view controller describing the selected item and let something else handle the sequence of what to do next. This results in much simpler view controllers that are responsible for doing fewer things, and you inherently end up with smaller, more testable classes.

Conclusion

Whenever I read posts or discussions about new frameworks or patterns for architecting iOS apps, my brain defaults back to basics and reminds me: “It’s just MVC.” I think sometimes there’s a tendency to overthink our architectural decisions and try to reinvent the wheel, but at the end of the day, the principles we need to build good software were written down a long time ago, and our job is to find the best ways to apply them. One of those principles is a clear separation between your views and your models, and in this post I’ve tried to demonstrate some of the patterns we’re using to facilitate and enforce that separation while building components that can be easily reused and restyled. Separating concerns is pretty much the whole story of software development, and hopefully this gives you a couple tools towards that end.