Building a Composable, Flexible, and Functional Layout in our iOS App

The Wayfair iOS app aims to have complete feature-parity with our website. This means that we need to support a variety of screen sizes and orientations in ways that — at least from an iOS frameworks point-of-view — are “custom”. To be specific, we need to support three distinctly different layouts, each with its own view hierarchy and behavior: iPhone portrait, iPad portrait, and iPad landscape.

We want to share code between these different layouts, so we need a way to construct them fluidly. Specifically, it should be possible to share layout code, but it may not always be advisable to do so, and there’s no way to know ahead of time which aspects of a layout will need to be customized.

UIKit provides a clear starting point to do this work (namely `UITraitCollection` and the `UITraitEnvironment` protocol), but a naive approach to consuming these values leads to highly procedural code with severe drawbacks: Constantly checking trait collection values inside of branching conditionals is error-prone and leads to view controllers with entanglements and poor composability.

Instead, we’ve been investigating a more functional approach to this problem based on applicative functors and the so-called “applicative style”.

Expressing our layout with applicatives will require us to master a few new concepts and operators, but will give us the ability to build our layout out of composable pieces — pieces which individually will support customization along any axis, but which can be snapped together into a larger layout regardless of the degree to which they are customized, or in what ways.

This post explains our research so far, and should provide an interesting introduction to the concept of applicatives for Swift developers with no previous functional programming experience.

Getting started

Let’s start by speaking more carefully about what we desire. We want to achieve some degree of “conditional layout” — that is, we want our layout to vary based on its environment.

Depending on one’s perspective, a view’s “environment” could be said to consist of many things, but let’s stick with the obvious for now: `UITraitCollection`. If we start from the idea that an instance of `UITraitCollection` constitutes the view’s environment, then we can write a type to express the idea of a traited value — a value that varies based on the current environment:

typealias TraitedValue<A> = (UITraitCollection) -> A

This is just a function type, akin to saying, “If you give me a trait collection, I will examine it and give you back an appropriate value of type `A`”.

It’s not clear how we’re going to compose and consume `TraitedValue`s yet, but let’s see if we enjoy the process of producing them.

Creating styles

Let’s take this new concept and apply it to a best practice that we are already comfortable with. We’ll write what is sometimes called a style guide, pattern library, or collection of “atoms”: A central library of styles, written in code, that we can apply across our app to give it a consistent appearance. This is the sort of thing you might typically implement as a series of constants in a constants file, a series of `class` or `static` properties that represent various style abstractions that go together, or perhaps various incantations of `UIAppearance`.

In our case, our style guide is made up of `TraitedValue`s — that is, our styles are sensitive to the current trait environment, which we don’t know at compile time. Thus, our style guide consists of functions, not constants:

There are some small wins here if we’re thinking critically: Being free functions, these styles can be used by any file that can see them; being short and stateless, the functions are easily testable, and being functions, these styles clearly embody the conditional layout concept that we’re aiming for.

However, as mentioned earlier, we haven’t yet done anything to help the consumers of these styles. In fact, building our final layout is arguably worse than it was before — breaking out all these functions has reduced readability and locality, and we keep having to thread an instance of `UITraitCollection` around just to get the values we need:

Sharing environments

Let’s make the leap to something more exotic. As mentioned earlier, we can add support to turn our `TraitedValue` type into an applicative functor, which is a functional programming construct closely related to a monad.

Informally speaking, applicatives provide the ability to retrieve values independently, and then combine them together inside a shared “context” or environment. (This is only one of the many ways of conceptualizing applicatives).

It is hard to see at first, but broadly speaking, we want to write isolated functions that define specific characteristics of our layout, then combine those functions together inside a context that is aware of the current trait collection.

Like many of the tastiest functional concepts, we can start from a relatively vague notion and let the type system guide us to an implementation. In our case, it is enough to know that the first step toward implementing an applicative is to write a function called `pure`.

Laws govern the nitty-gritty details of how `pure` must behave, but speaking informally, `pure` should take a plain value and return it, wrapped inside our applicative. This is the type signature we’ll need:

func pure<A>(_ value: A) -> TraitedValue<A>

Once you remember that a `TraitedValue` is really a function, the implementation of `pure` is straightforward — we need to immediately open a closure, which will be our return value:

func pure<A>(_ value: A) -> TraitedValue<A> {
return { _ in value }
}

In this case, the word pure itself is a useful mnemonic. When we “lift” a plain value into a `TraitedValue` using `pure`, we are saying that the value does not vary over the trait collection, which is why our closure ignores its input parameter.

We also must define `apply`, which is closely related to a functional `map`, but with a twist — our `apply`’s `transform` argument must itself be “lifted” into the world of `TraitedValue`. Take a look at the type signature we’ll need:

It’s actually much easier to mechanically implement this function than it is to think metaphorically about what this operation actually represents. If we continue to remind ourselves that a `TraitedValue` is a function, then it’s clear that we must begin the way we did previously in implementing `pure` — we need to immediately open a closure, which will be our return value.

What goes in the body of the closure? Well, we have a `transform` function tailored to transform a value of type `A`, and we have an `A`. However, both of these parameters are wrapped inside of `TraitedValue`s. How does one “unwrap” a `TraitedValue`?

As before, we must remember that a `TraitedValue` is really a function. To get the value from inside, we must invoke that function on an instance of `UITraitCollection`.

Fortunately for us, a `UITraitCollection` instance appears just as we need it, in the form of the argument to the `TraitedValue` closure we are attempting to return!

Cool! Now that we have this, we can lift a plain, one argument function into the land of `TraitedValue`s. We can then `apply` a lifted parameter, and our applicative plumbing will take care of threading a given trait collection through the composition of those functions. Now our code looks like this:

This is probably the hardest transformation to understand. We’ve taken a vanilla member function called `configureView` and lifted it into the world of `TraitedValue`s, thereby making it aware of whatever trait collection context it is eventually passed.

Once the function is lifted, we can pass it a lifted parameter — in this case, a `TraitedValue<UIColor>` that we defined in our style guide. I think of this as giving ordinary functions superpowers; we are suddenly making them aware of cross-cutting concerns they were previously ignorant of.

Chaining computations

We’re on the downhill slope now — there’s just a couple more things we need. First, we must define an operator so that we can migrate over to the so-called “applicative style” — an alternate way of composing applicatives that is much more readable:

All we’ve done is defined an infix operator that is a synonym for our `apply` function and forwarded the implementation. Now, instead of function names and parentheses building up on the front side of our expression, we can write this:

Almost there! There’s one more shortcoming we have yet to address. Currently, we can only `apply` values to functions that take a single argument. We want to be able to use many `<*>`s in a single expression. Thankfully, there’s a relatively simple way to implement function currying in Swift, which will let us `apply` over functions of any number of parameters.

Here’s currying implemented for functions of two arguments. We’ll use this in a moment:

First, let’s write one more little extension to hide the awkwardness of our “`TraitedValue`s are actually functions” duality. This isn’t strictly necessary, but it improves the readability of our syntax in two ways: `TraitedValue`s become parameters passed to a function on the view controller, which matches most developers’ mental models; and since we can safely assume that every view controller has a trait collection on hand, we save on some typing:

With these changes, we gain the ability to graft trait-collection-awareness onto any function from the outside. For example, in the code above, we didn’t have to alter the implementation of `configureView` at all to create `liftedConfigureView`.

In addition, we gain a simple way to define an environment-aware style guide of layout-related functions. We could define colors, fonts, margins, and even entire views as being dependent on the trait collection, and build our final layout from compositions of those values, collapsing them down into regular `UIKit` objects only at the time of our choosing. Furthermore, this technique scales wider (all characteristics of all types are customizable in this way) and taller (there is no limit to how many values we can compose this way) effortlessly.

This only scratches the surface of what is possible with applicatives. Consider other things in your codebase that a) might constitute an environment of some kind, and b) are passed around — aspects of branding? Analytics? Login state? There is probably useful work to be done in all of these areas.

We hope to deploy this technique into production in the near future and will report back with findings. Thanks for reading and let us know what you think in the comments below!

Thank you to the Wayfair iOS team as well as Stephen Celis from Point-Freefor providing feedback on early versions of this post.