UserInterfaceLevel, the future of floating apps?

raulriera

Raul Riera

Posted on June 7, 2020

UserInterfaceLevel, the future of floating apps?

Last year Shopify started developing the All-new POS application. There was a small problem, given that all screens are presented as full screen to the user we needed the ability to distinguish a modal screen vs the rest. iOS 13 had a great answer to this.

It feels like every new iOS version adds something to UITraitCollection or UIUserActivity. iOS 13 was no different. There was an handy small addition to UITraitCollection for apps with floating windows. That addition was userInterfaceLevel.

What is UserInterfaceLevel?

Apple describes it as the visual level for content in a window. The possible values are base, elevated, and unspecified. The first one being the window’s main content and the last one anything above it. This will be the perfect match for our need of modal windows having a dedicated background colour.

Using it to meet our requirement

import UIKit

public struct AdaptiveColor {
    /// See [UserInterfaceLevel](https://developer.apple.com/documentation/uikit/uiuserinterfacelevel) for more info.
    private(set) var base: (light: UIColor, dark: UIColor)
    /// The color used for content visually above your window's main content. If none is specified, the `base` color is used instead.
    ///
    /// See [UserInterfaceLevel](https://developer.apple.com/documentation/uikit/uiuserinterfacelevel) for more info.
    private(set) var elevated: (light: UIColor, dark: UIColor)

    public init(light: UIColor, dark: UIColor) {
        base = (light: light, dark: dark)
        elevated = base
    }

    public init(base: (light: UIColor, dark: UIColor), elevated: (light: UIColor, dark: UIColor)) {
        self.base = base
        self.elevated = elevated
    }

    public var value: UIColor {
        if #available(iOSApplicationExtension 13.0, *) {
            return UIColor { traitCollection in
                let isElevated = traitCollection.userInterfaceLevel == .elevated
                if traitCollection.userInterfaceStyle == .dark {
                    return isElevated ? self.elevated.dark : self.base.dark
                } else {
                    return isElevated ? self.elevated.light : self.base.light
                }
            }
        } else {
            return base.light
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The previous snippet is how Shopify’s Point of Sale application is handling dynamic colours. You should focus on line #24, we detect the current elevation of the user interface and decide which of the two colour sets to use for this particular colour.

Now, let’s say we want to change the background colour of a view all we need to do is:

view.backgroundColor = AdaptiveColor(base: (light: .white, dark: .black), elevated: (light: .lightGray, dark: .gray)).value
Enter fullscreen mode Exit fullscreen mode

And thanks to the excellent UIAppearance we can style our UINavigationController similarly how we styled the background view, and all our screens will be displayed with the correct colour.

Putting everything together into a project, it looks something like this:

Demo

Further reading

That is all!, as always you can find the full source code on GitHub

GitHub logo raulriera / technical-articles

Just a place where I can store demo projects for my technical articles.

💖 💪 🙅 🚩
raulriera
Raul Riera

Posted on June 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related