Managing UI Colours with iOS 11 Asset Catalogs

Arnold Sakhnov
Xero Developer
Published in
10 min readApr 26, 2018

--

At last year’s annual Apple developer conference WWDC, the majority of the iOS developer community was excited to play around with ARKit and all the immersive virtual experiences you could create with it. We, on the other hand, were most intrigued by one of the minor features of iOS 11 SDK which received almost no on-stage attention: named colours.

You’ve been able to use Xcode Asset Catalogs to manage your images and other file-based resources since iOS 7. But until recently, you were limited to homegrown or third-party solutions for managing the colours you use in your app’s UI.

Most developers opt for defining colour constants in code, under a Theme namespace, or something of similar nature. This solves the problem of referencing the right RGB values in your code, but doesn’t really help if you use Storyboards and XIBs, where you need to continue picking colours manually.

While this may not sound like a big problem, it becomes one at the scale of a sizeable mobile project, with multiple distributed teams working on it simultaneously. Mistakes happen, wrong shades of blue or grey inevitably slip into production builds, resulting in unhappy designers and frustrated developers.

It was important for us to keep the Xero app’s design language consistent and polished, while also allowing it to evolve over time. When a major release of iOS introduces a new style convention, it’s frustrating for a non-developer to hear that it can’t be immediately adopted, because a “simple” colour change to all buttons in the app is near impossible to do quickly and comprehensively.

This is where named colour assets come in!

Semantic Colours

The immediate benefit of adopting iOS 11’s Colour Assets is very clear: it makes it possible to define a colour constant that’s accessible from both your code and your Interface Builder documents.

UIColor(named: "BrandBlue")

Just like that, you’ve got a colour reference that’s truly reusable across your entire Xcode project, plus a human-friendly GUI to manage and browse through your palette.

An asset catalog with a typical app colour palette

While this alone is a massive improvement, you could take it one step further by putting a bit more thought into how you name and organise your colour assets. With names like BrandBlue and LightGrey, it’s still too easy for a developer to misuse or misunderstand the purpose of a colour in the context of the UI. When styling a placeholder in a text field, which -Grey do you pick? We wanted it to be impossible to make a mistake.

A simplified example of the “semantic naming” approach to colour asset management

By giving our colour assets semantic names, we reflect the meaning and the purpose of each colour, as defined by the design language. This not only frees up the developer from having to make assumptions about what colour is most appropriate for any given component: it also puts more power into the hands of the designers. When all buttons use the ButtonText colour semantic, re-styling them all in one fell swoop is easy, making drastic re-designs less painful and easier to plan.

Depending on how granular you want to get with naming your colours (as it requires careful thought to strike a good balance), you’ll inevitably end up with a number of semantics that resolve to the same underlying colour. That’s natural and doesn’t necessarily mean that such “duplicate” semantics need to be consolidated, as you want the flexibility to change unrelated things independently. For example, in our app’s new look, PrimaryText and NavigationTitle semantics are the same dark grey colour, but they resolved to dark grey and white respectively in the old design.

A way to solve this apparent “duplication” problem elegantly is to take advantage of the fact that Apple has designed Asset Catalogs with programmatic generation in mind. An .xcassets “file” is nothing but a collection of folders with JSON manifest documents that describe the assets contained within. In the case of named colours, there are no resource files in there either, there’s just JSON.

This means with a simple build-time script you could convert a straightforward semantic-to-colour mapping definition into a folder structure Xcode expects, and use the resulting Asset Catalog as normal at runtime. We opted for keeping the mapping in a Swift source file that we evaluate with the swift REPL at build time:

Voilà! Once you’ve got your pipeline set up, you can keep track of any changes to your colour semantic definitions with git and still take advantage of the power that colour assets deliver.

We went as far as extracting this into its own separate framework that we could share across different projects, as a “drop-in” design-approved colour scheme for any app.

However, this is also where we began to discover some of the rough edges of this feature. If you are interested in developing a similar setup for your app, it’s important you know that consistent and designer-friendly colour management comes at a cost. Below are some of the issues we’ve found with iOS 11’s Colour Assets and the ways you can deal with them.

Named Colours Across Bundles

Remember how we mentioned we were excited to extract our semantic colour references into a reusable framework? What felt like an inarguably good architectural decision turned out to have major side effects. The moment we linked our framework to an app and tried to refer to any of the named colours, they stopped working entirely.

What happened?

If you are familiar with the way, for instance, UIImage(named:) works, you’ll know what’s going on. In iOS apps, the main bundle takes precedence by default and could be the only place where your app looks for resources, unless told otherwise.

On the code side of things, there’s an easy fix. Whenever you call UIColor(named:), simply ensure to set the optional in bundle: parameter.

// A global property for easy reference
let colorFrameworkBundle = Bundle(identifier: "com.myfirm.MyUIColors")!
// In your ViewController:
label.colorText = UIColor(named: "PrimaryText", in: colorFrameworkBundle, compatibleWith: nil)

The problem is much less straightforward for colour references in Storyboards and XIBs. And, once again, if you’ve dealt with having to embed images across bundles, you’ll know what the issue is:

Interface Builder does not support referencing images that live outside the bundle of the Storyboard or the XIB, and that has been a problem for a long time. (And to some people, this has been one of the many reasons to not rely on Interface Builder altogether, though that’s a whole other topic in itself.)

Unfortunately, the way colour assets are represented in the generated XML is very similar: they do not contain any references to the bundle where the asset lives.

When you use a colour asset across bundles this way, it won’t fail completely though, unlike the images. You may notice how Xcode helpfully provides a fallbackColor, which contains the value the IDE retrieved from the catalog at the time when you picked the colour. At runtime, iOS will apply this fallbackColor, and you’ll see a warning in the console that’s something along these lines:

2018-04-26 17:30:30.009855+1200 MyApp[82987:11407859] WARNING: Unable to resolve the color named "PrimaryText" from any of the following bundles: com.myfirm.MyApp, com.myfirm.MyApp

This will become a problem the moment the asset is modified, since this static fallbackColor in XML will get out of sync with the true colour value, defeating the purpose of the whole feature in the first place.

It’s disappointing to see that Apple did not learn from the flaws of image support in IB and took the same shortcuts with implementing colour assets. Let’s hope that the most exciting minor feature of iOS 12 will be better multi-bundle interplay. rdar://35764499. (Update: it wasn’t. While Xcode 10 introduced some minor improvements to the way named colours are shown in IB, iOS 12 does not address any of the issues described in this article.)

The workaround for this in the meantime would be automatically copying the asset catalog with the colours into all bundles that need them, at build time. If you use Cocoapods to assemble your project, the podspec for your private colour library may contain something like this:

spec.resources = [
"MyColorLibrary/**/*.{xcassets}"
]

This will ensure that the asset catalog is added to the main bundle of your final product, thus becoming visible to all XIBs and Storyboards that reference it.

If you don’t use Cocoapods or if your project’s internal dependencies structure is sufficiently complex, you may need to invest in custom tooling to automate this process for you. No matter how you set this up though, ensure that you respect the “single source of truth” concept, whereby the copies of the original asset catalog can never be accidentally modified and committed independently.

Deferred Runtime Colour Application

Even if your app is a single-bundle project with a straightforward structure, and you aren’t interested in setting up fancy pipelines for generating asset catalogs on the fly, there are still other gotchas with using colour assets in their current form.

One crucial aspect, which could make you lose hours tearing your hair out if you aren’t aware of it, is the deferred runtime nature of colour assets when used in Interface Builder.

You can easily observe this by configuring a XIB or Storyboard that contains a named colour. You then modify or override that colour at runtime, with code:

The above setup appears to be perfectly reasonable: it’s something you’d do in the real world. In fact, it will work just fine if you don’t use a named colour in the XIB.

Named colours however will either refuse to get programmatically replaced altogether, or might keep getting overridden back to their original XIB value from under you at runtime, seemingly randomly and uncontrollably.

Note how the label’s DisabledText colour flicks back to PrimaryText on device rotation

Who is this UIKit poltergeist that’s doing that?

To save you the full paranormal investigation story, the answer is: UITraitCollection.

The way Apple has gone about implementing named colours in Interface Builder is that once they are applied to an element, they get “baked into” that element’s trait collection attributes. You may have used the little + button in Interface Builder to explicitly activate this behaviour for other properties:

This (little used) feature allows you to vary certain properties of elements automatically, based on the size class of the environment they are in. That’s quite useful for sizing constraints, as it allows you to avoid messy layout code with device-specific conditions, though this is of questionable value for colours.

Regardless, once a value is associated with a specific size class variant in IB, it’s written into the UITraitCollection store of the element, privately managed by UIKit. At runtime, the system will aggressively apply that stored value in its corresponding trait collection environment, whenever it sees fit, overriding any changes you may futilely make to it with your own code.

And, whether intentionally or not, named colours are put into that special trait-collection-associated category by default. Which means that once they are set in IB, they cannot be changed at runtime.

The solution at this moment is to simply not use named colours in Interface Builder in scenarios where you want to conditionally change them at runtime. The above code could be refactored to:

Note the use of the “Default” colour for the text label

However, it is doubtful that such extremely restrictive and unintuitive behaviour is intentional. Size-class-based variance should be opt-in and work consistently with the way it does for non-named colours. We hope that this also gets addressed in future iOS versions. rdar://36530826

Conclusion

Built-in colour asset management is a great addition to the iOS developer toolkit. It helps design-conscious mobile teams have better control over the way colour is used in the UI of their apps, and it brings designers and developers closer together.

The fact that named colours are a feature of Xcode Asset Catalogs means that custom pipelines to generate and synchronise colour palettes are easy to build, which can accommodate a wide variety of team workflows.

At the same time, support for colour assets is still new to iOS SDK, and it shows. Some of the bugs around cross-bundle support and runtime behaviour may cause friction, though these problems are by no means insoluble and seem likely to be addressed by Apple in future releases of iOS.

Talk to your mobile designer today about their wildest dreams of easy and consistent colour management. Asset Catalogs could be the answer to their prayers and a key to building beautiful apps with less effort.

-

Over a million small businesses, and their advisors are looking for the best cloud apps that integrate with Xero. Partner with us, and we’ll make sure they find yours.

--

--