Mastering iOS 14 Programming - Fourth Edition

By Mario Eguiluz Alebicto , Chris Barker , Donny Wals
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 2: Working with Dark Mode

About this book

Mastering iOS 14 development isn’t a straightforward task, but this book can help you do just that. With the help of Swift 5.3, you’ll not only learn how to program for iOS 14 but also be able to write efficient, readable, and maintainable Swift code that reflects industry best practices.

This updated fourth edition of the iOS 14 book will help you to build apps and get to grips with real-world app development flow. You’ll find detailed background information and practical examples that will help you get hands-on with using iOS 14's new features. The book also contains examples that highlight the language changes in Swift 5.3. As you advance through the chapters, you'll see how to apply Dark Mode to your app, understand lists and tables, and use animations effectively. You’ll then create your code using generics, protocols, and extensions and focus on using Core Data, before progressing to perform network calls and update your storage and UI with the help of sample projects. Toward the end, you'll make your apps smarter using machine learning, streamline the flow of your code with the Combine framework, and amaze users by using Vision framework and ARKit 4.0 features.

By the end of this iOS development book, you’ll be able to build apps that harness advanced techniques and make the best use of iOS 14’s features.

Publication date:
March 2021
Publisher
Packt
Pages
558
ISBN
9781838822842

 

Chapter 2: Working with Dark Mode

We all love it…well most of us do anyway, and those who do have been asking for it for a while now too. Apple first took the plunge into Dark Mode with macOS Mojave back in 2018, not only changing the way users interacted with the OS but also paving the way for developers to build native dark-themed apps for the first time.

Dark mode for iPhone wasn't announced until WWDC 2019, but we all knew it was coming, and with everything that AppKit had offered, we knew we were in for a treat with what UIKit would have to offer.

In this chapter, we'll cover everything you need to know to get up and running with dark mode in iOS and iPadOS; everything from taking an existing app and making the necessary adjustments to support dark mode, to all the little hidden extras that we can add in when building our app to ensure we give the user the best possible experience. We'll also touch on best practices too – taking note of the little things we can do that allow Dark Mode in UIKit to make our lives so much easier from the start.

The following topics will be covered in this chapter:

  • What is Dark Mode?
  • Working with views in Dark Mode
  • Working with assets
  • Further exploring Dark Mode
 

Technical requirements

For this chapter, you'll need to download Xcode version 11.4 or above from Apple's App Store.

You'll also need to be running the latest version of macOS (Catalina or above). Simply search for Xcode in the App Store and select and download the latest version. Launch Xcode and follow any additional installation instructions that your system may prompt you with. Once Xcode has fully launched, you're ready to go.

Download the sample code from the following GitHub link: https://github.com/PacktPublishing/Mastering-iOS-14-Programming-4th-Edition.

 

What is Dark Mode?

In this section, we'll start by taking a look at what exactly Dark Mode is, how we can use it, and what it can do not only for the end user but also for developers. We'll cover everything from enabling it on our devices to using environment overrides in Xcode and developer options in the simulator.

Understanding why we would need Dark Mode

As I covered in the introduction of this chapter, most of us have been craving dark mode in iOS for a very long time now. Us developers got our Xcode fix back in 2018 – but one of the burning questions I've been asked many times (especially in the past 12 months) is…why?

It could be down to something as simple as the time of the day. Satellite navigation systems have been doing it in our cars for years – as soon as the sun goes down, our system switches, and a more relaxing, subtle version of our road home pops up on our screen – so why not do that for our apps?

Well, it turns out that some apps have already been doing that for a while (to a degree), while not necessarily offering an automatic nocturnal mode. The Twitter app for iOS offered a "dark mode" option long before the WWDC 19 announcement.

Let's stop for a moment and think about the logic behind such a control, along with everything you'd need to change to achieve this. I'm sure a company as big as Twitter has written their own internal framework to handle this, but under the hood, it's basically going look a lot like the following:

var isDarkMode = false
var profileLabel: UILabel? {
    didSet {
        profileLabel?.textColor = isDarkMode ? .white : .black
    }
}
var profileBackground: UILabel? {
    didSet {
        profileBackground?.textColor = isDarkMode ? .black : .white
    }
}

Everything will have to be taken into consideration, from the text color to the drop shadows that you might have decorating your UIButton or UIViews.

The background is a massive change to consider too. One common pattern that a lot of iOS developers follow is to quite simply develop a brand-new app on top of a white canvas; from here, we don't need to worry about controlling the background color or keeping track of it with an IBOutlet – it's simply the tablecloth for the rest of our app to sit on.

With implementing a dark mode feature, everything needs to be changed – even asset images that sit proudly on one style of background could be lost in another. Let's take a look at some of the developer features that come bundled with Xcode when implementing Dark Mode.

Core developer concepts of Dark Mode

Let's start by taking a look at how we go about developing with Dark Mode by switching to it on our device. If you've not already done this, you can simply toggle it by going to Settings | Display & Brightness, and you should be presented with the following screen:

Figure 2.1 – Display and brightness

Figure 2.1 – Display and brightness

You'll also notice the Automatic toggle option too, giving us the ability to use either Sunset to Sunrise or a custom schedule, which will automatically switch between light and dark appearance (just like our sat nav).

Now that we've got that covered, let's take a look at some options given to developers in the iOS simulator. Let's start by taking the following steps:

  1. Open up Xcode.
  2. Launch the simulator (Xcode | Open Developer Tool | Simulator).

    In a slightly different location than the end user's version of iOS, you'll find the dark toggle under the developer settings (Settings | Developer | Dark Appearance):

Figure 2.2 – Dark mode developer settings

Figure 2.2 – Dark mode developer settings

Rather than a fancy interface like we saw earlier, we're presented with just the standard toggle. Let's take a look now at what we can do as developers with Dark Mode.

Dark mode from inside Xcode

Now that we've taken a look at how iOS handles switching to dark mode, let's have a look at how we, the developer, can do the same in Xcode.

Out of the box, all new projects building against the iOS 13 SDK will automatically support dark mode; however, building against any earlier SDKs won't.

This helps out a little as your existing app may not have all the necessary tweaks to support dark mode yet and you don't want to release an update to find you have broken your app for those now running dark mode.

However, if you update your project to the iOS 13 SDK, then you could potentially run into this problem, but don't worry, we'll cover getting your existing app ready for dark mode later, in the Migrating existing apps to Dark Mode section in this chapter.

Let's start by having a look at storyboards – we all love them (or hate them) but the one thing they have done over the years is present themselves on a whiter-than-white canvas.

Let's get started:

  1. Launch Xcode and create a new Single View - Storyboard project.
  2. Call this anything you want (I'll call mine Chapter 2 - Dark Mode).

    You can either follow along throughout this chapter or download the sample code from GitHub.

Once created, click on Main.Storyboard and you should be presented with the following:

Figure 2.3 – Xcode interface style

Figure 2.3 – Xcode interface style

I've highlighted in the preceding screenshot our area of interest – here, we have a preview toggle for both light and dark appearances from within the storyboard, so at a quick glance, we can see what the objects we've added to our canvas look like without the need to launch the simulator.

Now, this won't always help us as some of our UILabels or UIButtons could be decorated programmatically. However, it's a great start and will most definitely come in useful during the development cycle of any app.

Let's take a look at our labels in action. Here, we've added a UILabel straight out of the box. Light Appearance is selected, and the label looks just how we are used to seeing things at this stage:

Figure 2.4 – Main storyboard

Figure 2.4 – Main storyboard

Now, let's switch the toggle to Dark Appearance and see what happens:

Figure 2.5 – Main storyboard – dark mode

Figure 2.5 – Main storyboard – dark mode

As if by magic, our canvas enters dark mode, and the color of our UILabel is automatically adjusted. We can see straight away, without the need to compile or run the app on a device or in the simulator, how it will look with each interface style.

I guess the million-dollar question is how did iOS know to switch the color of the UILabel's font? Good question, and we'll cover that in more detail in the Working with views and Dark Mode section later in this chapter.

However, as I mentioned earlier, there are going to be occasions where you'll need to test your app in the simulator. Labels and views will not always be static and could be generated dynamically – this is where environment overrides come in.

We'll start by launching our app in the simulator. Once successfully launched, you should see the following highlighted option available in Xcode:

Figure 2.6 – Environment Overrides

Figure 2.6 – Environment Overrides

Click on this icon and you'll be presented with the Environment Overrides popup. Here, you'll have the option to toggle the Interface Style overrides, which in turn will allow you to choose between light and dark appearance.

If you flick the toggle and switch between each option, you'll see your app in the simulator automatically update without the need to close your app, change the settings, and re-launch. Very nice indeed – thanks, Xcode!

One last little thing to point out before we move on: we mentioned previously that existing apps built with previous iOS SDKs won't be affected by dark mode, but should you choose to update your app to the iOS 13 SDK, you may run into a couple of issues.

Tight deadlines and urgent bug fixes might not necessarily give you the chance to adopt dark mode in your app, so Xcode gives you the option to force light appearance regardless of the user's preference.

In Info.plist (or the Info tab under our project settings), add the following key with the value Light:

UIUserInterfaceStyle
Figure 2.7 – Info.plist – User Interface Style

Figure 2.7 – Info.plist – User Interface Style

You'll now see that even with the environment overrides, you won't be able to switch to dark mode.

In this section, we got up and running with Dark Mode in iOS and, more importantly, Xcode, and learned about the little things that Xcode does to get us ready for developing our apps in both light and dark appearance. In the next section, we'll begin to look at how Xcode handles views and introduce ourselves to semantic "dynamic" colors.

 

Working with views in Dark Mode

So far in this chapter, we've covered not only what dark mode is but also what it has to offer from a development perspective.

In this chapter, we're going to deep dive further into dark mode by looking at how Xcode dynamically handles our UIViews (and objects that are sub-classed from UIViews).

We'll start by understanding the core concept behind adaptive and semantic colors, and by following a simple pattern, Xcode can do so much of the heavy lifting for us.

We'll then dive further and take a look at the various levels of semantic colors available to us, including primary, secondary, and tertiary options, but more importantly, when we would be expected to use them.

What are adaptive colors?

For me, this was a major step in getting developers on board with designing and developing their apps for dark mode and, of course, it was well within Apple's interest to make it as seamless as possible for the developer.

Adaptive colors are a way of defining a single color type or style for a particular appearance. Let's start by diving straight into Xcode and seeing this for ourselves:

  1. Head back on over to the project you previously created and highlight the UILabel we added in.
  2. Now, take a look at the Color property in the Attributes Inspector window:
Figure 2.8 – Label properties

Figure 2.8 – Label properties

You'll notice that the color selected is Default (Label Color)Label Color is our adaptive color.

But what does that mean? Actually, it's very simple: it means that for one interface style it's one color, and for the other, it's a different color.

In the case of our previous example, our UILabel was black in light mode and white in dark mode – makes sense, right?

Well, to a degree is does, but surely it depends on what type of background our UILabel sits on – let's take a look.

Back in our storyboard, highlight the background of our view and again head over to the Attributes Inspector window:

Figure 2.9 – Background color properties

Figure 2.9 – Background color properties

Again, here we have our adaptive color, System Background Color. Xcode is doing all the work for us when we need to switch appearances.

The preceding parts in the section are a great example of contrasting between two primary colors (black and white used in our labels), which itself is the stereotypical understanding between what colors should be in light and dark appearance – but we're not always going to be using black or white, are we?

So, Apple has updated all their available system colors to be adaptive. Let's take a look.

Head back over to Xcode and highlight our UILabel, and change Color to System Indigo Color:

Figure 2.10 – Font color properties

Figure 2.10 – Font color properties

Now, let's switch between light and dark mode using the toggle in Xcode's storyboard. What do we see? The color indigo, just as we expected:

Figure 2.11 – Light mode with Indigo text color

Figure 2.11 – Light mode with Indigo text color

The following screenshot shows the screen with dark mode:

Figure 2.12 – Dark mode with indigo text color

Figure 2.12 – Dark mode with indigo text color

However, each system color has been specifically adapted to each appearance. Let's take a look at the RGB values for each appearance:

  • Dark: R 94: G 92: B 230
  • Light: R 88: G 86: B 214

Although there is a subtle difference in each RGB value, it has a massive effect in terms of appearance and allowing it to stand out against other adapted colors defined by Apple (such as our system background color).

Now that we've learned all about adaptive colors, let's take a look at semantic colors and how Apple helps us pre-define not only the colors we want to use but also where a type of color should be used.

What are semantic colors?

To answer the questions of this section requires us to take a quick look back at what we already covered in the What are adaptive colors? section, because we've already touched on semantic colors.

Remember Label Color from our UILabel and System Background Color? These are all semantic colors – not so much by physical color, but more by their definition and intended purpose.

With semantic colors, Apple has created a whole pre-defined range of adaptive colors that are designed specifically for objects such as labels, backgrounds, and grouped content such as table views. Each of these has additional primary, secondary, and tertiary variants.

Let's put this into practice and update our current Xcode project:

Figure 2.13 – UILabel with semantic variants

Figure 2.13 – UILabel with semantic variants

I've added a couple more UILabels here and just done a little bit of re-arranging (nothing special), but what I have done is set the semantic variant for each Label with a corresponding variant – let's take a look:

Figure 2.14 – Color options

Figure 2.14 – Color options

If we expand the color options for our UILabel, we can see a list of all the pre-defined adaptive/semantic and system and variant colors available to us. I've highlighted the colors I've chosen for each of the new labels.

Now, let's switch the appearance to dark and see how it looks:

Figure 2.15 – Semantic labels in Dark Mode

Figure 2.15 – Semantic labels in Dark Mode

Let's go a step further and add some more adaptive content in there. Here, I've dropped in a UIView to act as a separator between content, a UIButton, which will be a URL link, and a UITableView:

Figure 2.16 – Separators and other background colors

Figure 2.16 – Separators and other background colors

I've assigned the following semantic colors to each of my new views:

  • Separator: Separator Color
  • Button: Link Color
  • Table View: Group Table View Background Color

Let's fire this up in the iOS simulator and see side by side how it looks. You'll notice something interesting:

Figure 2.17 – Table view in light and Dark Mode

Figure 2.17 – Table view in light and Dark Mode

In the light appearance, you can clearly see the table view's group background color against the system background color; yet if we take a look at this in the dark appearance, you don't see it as much. That's because with a much darker primary background color, the separation isn't needed as much; the black on black doesn't get lost and looks more natural, whereas white on white does.

This all looks great built into Interface Builder, but now let's take a look at how we would do this programmatically.

Using the programmatic approach

Let's start by creating IBOutlets for each of our objects. If you're unfamiliar with creating an outlet, simply, in ViewController, we do the following:

  1. Declare all our outlet properties first.
  2. Then, from the IBOutlet connector (just to the left of your property), press Command + Primary Cursor Click.
  3. Hold and drag this to the UIView or object you want to connect to.

    Opening both Interface Builder and ViewController in separate windows will really help this process too:

    Figure 2.18 – Creating an outlet

    Figure 2.18 – Creating an outlet

  4. We'll need to create these in our ViewController.swift file just inside the class declaration. Copy the following highlighted code into your class:
    class ViewController: UIViewController {
        @IBOutlet weak var headerImageView: UIImageView!
        @IBOutlet weak var primaryLabel: UILabel!
        @IBOutlet weak var secondaryLabel: UILabel!
        @IBOutlet weak var tertiaryLabel: UILabel!
        
        @IBOutlet weak var linkButton: UIButton!
        
        @IBOutlet weak var separatorView: UIView!
        
        @IBOutlet weak var tableView: UITableView!
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    }
  5. Now, we can programmatically assign our colors. Inside the viewDidLoad() function, add the following highlighted code:
    override func viewDidLoad() {
        super.viewDidLoad()
            
        primaryLabel.textColor = UIColor.label
        secondaryLabel.textColor = UIColor.secondaryLabel
        tertiaryLabel.textColor = UIColor.tertiaryLabel
            
        linkButton.titleLabel?.textColor = UIColor.link
            
        separatorView.backgroundColor = UIColor.separator
            
        tableView.backgroundColor = UIColor.systemGroupedBackground
            
    }

If you launch the app in the simulator, you'll see that everything should stay the same. If we really want to test our logic, head back on over to Interface Builder and set one of our UILabels to System Green Color. Re-run the app and watch the programmatic code take precedence and override Interface Builder.

In this section, we've looked at how working with views for Dark Mode either in Interface Builder or programmatically is possible with the use of adaptive and semantic colors. We also looked at and understood the value behind using color variations and saw the effect they both have in both light and dark appearance. In the next section, we'll take a look at the asset catalog and how we can create custom adaptive colors and images for use in our app.

 

Working with the asset catalog for Dark Mode

Since the ability to add colors to the asset catalog became available back in Xcode 9, there is now even more reason to take full advantage of one of Xcode's prized assets.

In this section, we'll look at how we can use the asset catalog not only to create our custom colors but also to create our own adaptive colors and images, allowing us to harness the power of Xcode when developing dynamic appearance applications.

Using custom adaptive colors

Sticking with our current project, head on over to the file inspector, and highlight the Assets.xcassets folder. With the following layout visible, click on the + button highlighted in the following screenshot and select New Color Set from the list of options:

Figure 2.19 – Creating a color set

Figure 2.19 – Creating a color set

Add in another three color sets and name them the following:

  • brandLabel
  • brandSecondaryLabel
  • brandTertiaryLabel

Highlight brandLabel, and then highlight the option in the central asset preview window. Notice the list of attribute options now made available to us in the Attributes Inspector pane:

Figure 2.20 – Adding a color set

Figure 2.20 – Adding a color set

As you can see, we can now define the brandLabel color that we want to use. But first, let's make it adaptive. In the Attributes Inspector pane, change Appearance from None to Any, Light, Dark.

You'll have noticed on the dropdown that there was another option of Any, Dark, so let's go through what this means:

  • None: This is a default color and won't be adaptive to your selected appearance.
  • Any, Dark: In this scenario, Any will support legacy versions of your app, along with any other variations that aren't dark (so light, basically). Dark will be dark…
  • Any, Light, Dark: Same as the preceding but will allow you to specifically select a value for legacy and light (along with dark).

So, with that covered, let's add some colors. Now, as mentioned before, this is where you can be really specific with your color choices, either by personal preference or brand guidelines you have to follow. For me, I'm just going to click Show Color Picker and pick my favorite colors:

  • Tangerine for Any (Legacy) and light
  • A more subtle Cantaloupe for dark:
Figure 2.21 – Choosing a color

Figure 2.21 – Choosing a color

Do the same for brandSecondaryLabel and brandTertiaryLabel, remembering to slightly alter the colors based on the semantic purpose you intend to use them for.

Once you've done that, head back on over to Interface Builder and highlight primaryLabel, then bring open the options of colors from Attributes Inspector. You should see the following:

Figure 2.22 – Default label color

Figure 2.22 – Default label color

All the color sets you created in the asset catalog are available to use right there in Interface Builder. Go ahead and add them in for each label and see how they look by switching appearance in Interface Builder:

Figure 2.23 – Color set, light mode versus dark mode

Figure 2.23 – Color set, light mode versus dark mode

With that done, you've created your very own adaptive, semantic and dynamic colors for your app – all within the power of Xcode.

If you wanted to use the colors programmatically, you can do that by simply referring to the asset name in a couple of different ways.

First is a direct reference to the name of the assets:

primaryLabel.textColor = UIColor(named: "brandLabel")

Alternatively, you can select the asset directly from the media library by pressing Shift + CMD + M and selecting show color palette from the icon options and selecting the color you want.

This will insert the color from the assets catalog as a swatch, directly inside your code:

Figure 2.24 – Assigning a color set programmatically

Figure 2.24 – Assigning a color set programmatically

Or another option, if you really wanted to keep your code clean, would be to create an extension of UIColor allowing you to define your own property:

extension UIColor {
    static var brandLabel: UIColor {
        return UIColor(named: "brandLabel") ?? UIColor.label
    }
}

This can now be used just like this:

primaryLabel.textColor = UIColor.brandLabel

This is a nice, clean, and manageable way to look after your custom color sets programmatically, but this really is a personal preference, and each to their own. If you're working with a large alternative color guideline, making the change to a primary color in one extension will roll the change out to your entire app without the worry of missing a label or two.

Next, let's take a look at the same approach but for images.

Using custom adaptive images

We've learned a lot about how the asset catalog works with adaptive images from the previous section, Custom adaptive colors, and luckily, we can take full advantage of that in creating adaptive images for our project.

In the same way that we created a new color set, let's follow these steps:

  1. Head on back over to Assets.xcassets.
  2. Create a new image set:
    Figure 2.25 – New image set

    Figure 2.25 – New image set

  3. Name your new image header, highlight it, and change the appearance in the Attributes Inspector window to Any, Dark. You should now see the following:
Figure 2.26 – Add new image set

Figure 2.26 – Add new image set

When adding an image to the image catalog, you'll be given the option for adding 1x, 2x, or 3x images – these are different image scales you can set for various screen sizes. For further information, see the following from Apple's documentation.

For this example, we are going to add in two different images to the 2x option: one for Any and the other for Dark. You can grab the images I've used from the sample project found in GitHub or choose your own – it's up to you. From the Finder, simply drag and drop the images into the 2x placeholder inside Xcode. You should see the following once done:

Figure 2.27 – New image set variants

Figure 2.27 – New image set variants

Now, head back on over to your storyboard and add in a UIImageView to your project. Add this to the top of ViewController to act as a header.

Once in place, head on over to the Attributes Inspector pane and select the dropdown for the Image option – there, you should see your newly created asset, header:

Figure 2.28 – Setting header from the image set

Figure 2.28 – Setting header from the image set

Select this and take a look (depending on the size of the image you chose, you may need to set Content Mode to Aspect Fill – these options can also be found in Attributes Inspector).

Run the simulator and have a look at everything you've achieved so far in this chapter, remembering to switch from light to dark appearance by using the environment override in Xcode… looks pretty good, right?

Figure 2.29 – Header light mode versus dark mode

Figure 2.29 – Header light mode versus dark mode

Just like we did with color sets, we can of course handle this programmatically, should we wish. Let's add another extension to our app to handle this for us:

extension UIImage {
    static var header: UIImage {
        return UIImage(named: "header") ?? UIImage()
    }
}

We can again use this in just the same way as before:

headerImageView.image = UIImage.header

We do this by assigning our header image directly onto our UIImageView.

In this section, we harnessed the power of the asset catalog to allow us to create custom adaptive and dynamic colors and images for our app. In the next section, we'll take a look at how best to update a legacy app to support dark mode with everything we've learned so far, and also how best to identify the little things we can do to futureproof our apps for various appearances.

 

Further exploring Dark Mode

In the previous sections, we gave you a lot to think about when either creating or migrating existing apps to Dark Mode under specific circumstances. In this section, we'll take a look at a couple of little "nice to knows" that should always be in the back of your mind when approaching Dark Mode.

Using Dark Mode with SwiftUI

With the announcement of SwiftUI back in June 2019, a massive shift in focus on UI-based development took place. Released at the same time as Dark Mode, and as expected, SwiftUI takes full advantage of switching appearances.

Let's start by taking a look at how we could detect dark mode programmatically in SwiftUI:

  1. First, we'll create an environment variable that allows us to access the current state of the appearance of the device:
    @Environment(\.colorScheme) var appearance
  2. Next, let's use a simple ternary operator to display some text based on the current appearance:
    Text(appearance == .dark ? "Dark Appearance" : "Light Appearance")

It really is that simple.

Now, let's have a look at the options available to us in the automatic preview window. SwiftUI uses PreviewProvider, which allows us to display dynamically what we are designing/developing.

To enable Dark Mode in PreviewProvider, simply add the following highlighted code and hot refresh:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.colorScheme, .dark)
    }
}

Here, we've added a modifier to set the .colorScheme environment variable to .dark. If we want to preview both .light and .dark side by side, we can simply do the following:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView().environment(\.colorScheme, .light)
             ContentView().environment(\.colorScheme, .dark)
        }
    }
}

Tip

To learn more about SwiftUI, take a look at Learn SwiftUI, available from Packt Publishing: https://www.packtpub.com/business-other/learn-swiftui.

Programatically handling changes with trait collection

During the development of your new app, there could be a couple of occasions where you might need to handle a specific scenario based on the current appearance. However, we'll need to take a slightly different approach to this than what we did with the SwiftUI example previously.

The interface style is part of the UITraitCollection class (which, in turn, is part of UIKit). We can do a conditional check against a value using the following anywhere in our ViewController:

traitCollection.userInterfaceStyle == .dark

Unlike SwiftUI, we can't just use a simple ternary operator as there are more than two values for userInterfaceStyle:

public enum UIUserInterfaceStyle : Int {
    case unspecified
    case light
    case dark
}

Unspecified is an option too (think Any, back in our asset catalog), so it's best to use another approach when detecting changes to our interface style.

Let's start by heading back into our ViewController.swift file and adding in the following override function:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    // Logic here    
}

This override is called whenever a change is made to a trait (such as appearance). From this, we now action any changes we would like to make, but the problem we have is traits are used for more than just appearances, and this override could be called for a variety of reasons.

So, if we are looking particularly for changes in our appearance, we can use the previousTrait property passed into our delegate function and compare against the current system trait – if there is a difference, we know the appearance has changed. Let's take a look at how we'd do this:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    let interfaceAppearanceChanged = previousTraitCollection?.hasDifferentColorAppearance(comparedTo: traitCollection)
}

By using the hasDifferentColorAppearance method, we can now easily compare the previous trains against the current one to see whether there have been any changes – the resulting method returns a Boolean, so we can use this at our convenience.

Specifying an appearance for views, ViewControllers, and windows

You may, in some circumstances, wish to specify an appearance based on a particular area of your app or if you are migrating to dark mode (but need a little more time for a certain feature). Simply drop in the following appropriate code to meet your desire.

Views

Here, we'll create and instantiate a basic UIView:

let view = UIView()
view.overrideUserInterfaceStyle = .dark // .light

We assign either a light or dark value.

ViewController

If we wanted to do this in a UIViewController, we would simply just do the following:

overrideUserInterfaceStyle = .dark

Again, we assign either a light or dark value (usually within viewDidLoad()).

Window

If we need to access the current window, we could do so as follows:

for window in UIApplication.shared.windows {
    window.overrideUserInterfaceStyle = .dark
}

(This is not a recommended approach and you would be hard-pressed to find any real reason to want to do this…)

Accessibility in Dark Mode

Ask around and someone will joke about how Dark Mode has existed in iOS for years, either as the Classic Invert or Smart Invert accessibility feature. I even had it on one of my slides at a conference about 2 months prior to Dark Mode being officially announced.

But with this in mind, a lot of things started to be said about accessibility in iOS – some comments referring to Dark Mode as "Apple finally supporting accessibility," which I have to be honest makes me very sad.

Accessibility has always played a massive part in iOS regardless of the appearance – but, even with the introduction of Dark Mode, this still goes unchanged as Dark Mode supports all accessibility features.

If you refer back to an earlier section in this chapter, Core development concepts in Dark Mode, you'll remember that we mentioned the option to schedule our light and dark appearances – much like you could with Nightshift that was introduced in iOS 9, again another element with a focus on accessibility.

In this section, we went a little outside of the box with regard to Dark Mode and stepped away from the basic implementation, allowing us to look at the wider options available to use and things to think about when implementing Dark Mode in our apps.

 

Summary

In this chapter, we've covered a lot about Dark Mode – not only from a programmatic perspective but also the theory behind the appearances and purpose of colors used within our apps.

We started by taking a look at how Xcode and iOS are set up for Dark Mode, learning about the environment overrides used in Xcode, and how we can even switch appearances in our storyboard while developing.

Next, we covered adaptive and semantic colors, and learned not only how these are used with Apple's default system colors but also how we can create dynamic and adaptive color sets ourselves.

Following on from what we learned about color sets, we applied this to images and harnesses the power of the assets catalog.

Finally, we covered some "great to know" topics, such as Dark Mode in SwiftUI, programmatically hailing appearances, and accessibility.

In the next chapter, we'll take a look at lists in iOS 14, covering everything you need to know about UITableViews and UICollectionViews.

 

About the Authors

  • Mario Eguiluz Alebicto

    Mario Eguiluz Alebicto is a software engineer with over 15 years of experience in development. He started developing software with Java, later switched to Objective-C when the first iPhone delighted the world, and now, he is working with Swift and involved in backend technologies. He loves to code, build exciting projects, and learn new languages and frameworks. Apart from software development, Mario loves to travel, learn new hobbies, practice sports, and considers himself a hardcore gamer, which he has been since he was a child.

    Browse publications by this author
  • Chris Barker

    Chris Barker is an iOS developer and tech lead for fashion retailer N Brown (JD Williams, SimplyBe, Jacamo), where he heads up the iOS team. Chris started his career developing .NET applications for online retailer dabs (now BT Shop) before he made his move into mobile app development with digital agency Openshadow (now MyStudioFactory Paris). There, he worked on mobile apps for clients such as Louis Vuitton, L'Oréal Paris, and the Paris Metro. Chris often attends and speaks at local iOS developer meetups and conferences such as NSManchester, Malaga Mobile, and CodeMobile.

    Browse publications by this author
  • Donny Wals

    Donny Wals is a passionate, curious, iOS developer from The Netherlands. With several years of experience in building apps and sharing knowledge under his belt, Donny is a respected member of the iOS development community. Donny enjoys delivering talks on smaller and larger scales to share his knowledge and experiences with his peers. In addition to sharing knowledge, Donny loves learning more about iOS, Apple's frameworks and development in general. This eagerness to learn has made him into a versatile iOS developer with knowledge of a significant number of Apple's frameworks and tools. During WWDC you will often find Donny binge-watching the talks that Apple engineers deliver to introduce new features and frameworks.

    Browse publications by this author
Book Title
Unlock this book and the full library for FREE
Start free trial