Advanced baseline grids for iOS

This is a continuation and refinement of the work I covered in Codifying style guides in Swift, wherein I claimed following:

  1. There is no such thing as a “final” style guide
  2. Change has to be accommodated in the implementations of style guides
  3. Styles should not be defined via interface builder (IB), and if they are, they should be diligently re-set via code
Snippets from the World Surf League content style guide

After launching Fantasy Movie League for iOS, I turned my day-to-day development efforts back to the World Surf League. Back to Objective-C, and a code base with the comforting personality that surfaces over years of development.

The WSL project plays an important role in my strong belief that we can do a better job of codifying style guides; mere days before the scheduled launch of the original iOS App, the style guide pivoted significantly and we spent a good chunk of time re-skinning. Anyone who has been through that kind of effort knows it’s hard to feel like you caught everything, and it’s generally a pretty unpleasant experience.

Because of the nature of the partnerships we forge with folks like the NFL and the WSL, we’re frequently developing the same code base for years at a time. That means that we’re doing that re-skinning work sooner or later regardless. So we have a future minded approach to our development, and we’re constantly on the lookout for incremental improvements, and better ways of doing things.

With the designers over at the WSL tackling new style guides, it was time to build on and refine the FML style guide implementation, and tackle some more advanced challenges.

Baseline Grids

Grid systems are common in digital development, but usually exist only in one dimension. The design team at the WSL wanted to build on some of the great work done by Medium, Airbnb and others, and formalize the vertical spacing of the UI. Enter baseline grids:

(See also Designing Faster with a Baseline Grid)

The challenge for those implementing such a grid is that it takes work. And understanding of crusty old typography. But I took the Twitter hint from style guide author Zak, and dived in:

The problem boils down to how vertical spacing is measured between text and other elements in your UI, and how you need to adjust it to arrive at the correct spacing as defined by the grid. Let’s say you have an 8pt baseline grid and you have two UILabels. Drop a Vertical spacing constraint between the two, say 16pt, and you’re done right?

Top label spaced 16 above bottom label

Not so. Whereas UI constraints are relative to the bounding box of the UILabel, the baseline grid is defined using baseline to cap height. Go-go-typography 101:

Baseline to cap height is very different from bounding box

So, that simple little 16pt constraint between our two labels needs to have the following subtracted from it:

  1. To adjust for the top label: the space below the baseline, i.e. line height minus ascent
  2. To adjust for the bottom label: the ascent minus cap height

And both of those measurements are dependent on the specific font size, and paragraph line height of each label. And if you’re doing it right, those can vary depending on the class size of the App UI, which can change.

Ok. Deep breaths.

The Code

First some utilities for doing that math:

Those class functions take a enumerated UI element such as ASPUITextElementFantasyAthleteProfileName, map it to a typographical style ASPUIElementStyleTitle2, and get the font that is defined for it. The trait collection parameter is important for allowing us to determine the class size we’re rendering within, and consequently, which stepped font size we’re dealing with:

That sneaky little line at the bottom takes our style guide font size, and nudges it up or down depending on the OS accessibility settings for text size.

Next, we need to know how big our spacing should be between the two labels. That, too, is dynamic:

Depending on the class size, again pulled from the trait collection parameter, and the requested size (tiny, small, large), we get a target number.

Now, in the view where I configure the table cell labels, we can do the following:

The traitCollectionDidChange function is called as the interface environment changes, e.g. when the app is snapped to a different size on iPad. The renderHeatIntoCell function is called by the assembling table view, using tableView.traitCollection for the traitCollection parameter (using the table cell trait collection won’t work initially because it’s not been configured for the correct context yet).

The magic happens in handleMargins where we get hold of a class size aware margin sizes, set the margins for the cell contentView (being careful to adjust based on the top and bottom labels), and adjust the vertical space constraint constant between the two labels themselves.


iPad Pro: adapting to accommodate Split Screen Multitasking

Same code, different UI context: Stepping down font sizes, line height AND margins

Conclusion

This is a pain in the ass. But implemented in this way, it’s less so. Your style guide should be implemented in a dynamic way such as this, regardless of whether or not you’re shooting for baseline grid compliance, and here we’re taking advantage of that programmatic access to achieve the promised visual cohesion and structure.

The new content style guide is live in the World Surf League iOS App, and we’re working on rolling out an App-wide style refresh, again building on this work, right now.

Questions or comments, find me Ben Dalziel or working Sly Trunk