Implementing Dark Mode on iOS

Session 214 WWDC 2019

Hear from the UIKit engineering team about the principles and concepts that anchor Dark Mode on iOS. Get introduced to the principles of enhancing your app with this new appearance using dynamic colors and images, and add an experience that people are sure to love.

[ Music ]

[ Applause ]

Good morning.

Welcome to Implementing Dark Mode in iOS.

I'm Kurt. I'll be presenting with Tyler.

Now, when we added Dark Mode to iOS 13, we looked at every piece of the UI and made it look great in this new dark appearance.

It's a whole new look for iOS.

Now it's your turn.

We're here to help you implement Dark Mode in your apps.

It's easy and we'll demonstrate that to you with some live demos.

It's also built on top of a very flexible and powerful system.

So whatever you imagine that your app should look like in Dark Mode, you can achieve it.

First, let's talk about the design of Dark Mode.

Now, I'll start by showing an example.

Here's settings of a very familiar app.

You see that in Dark Mode, it's the same app in the same layout.

It works the same way.

It just has a different appearance.

So, we've taken the iOS design system and extended it for the dark appearance.

When we did that, we considered three things.

First and most important, colors.

iOS apps traditionally have hard coded all of their colors.

You specify the RGB value of every piece of your UI.

Now that we have Dark Mode, almost every one of those colors needs to change.

And since there are so many colors, we need to keep organized.

We do that using semantic colors.

These are colors that have a name that explains what they signify.

Our text label here uses the color label.

That's a default color for text and labels.

Our background is system grouped background.

That's because we're in a grouped table view, and UI table view gives us that by default.

Finally for this icon, we're using system blue.

It's not just pure blue, but it's tweaked to fit with the rest of the colors.

So, when we implement UI, we pick an appropriate semantic color for each piece.

When we switch back to light mode, we're still using the same colors.

But because these colors are dynamic, they can have different values in light and dark.

The great thing is when you use these semantic dynamic colors, UI kit does the work for you.

You don't have to think about what mode you're in and you don't have to do any work when the mode changes.

Now, our design system provides a whole new palette of colors.

I'll give some examples.

We have a stack of background colors here designed to work together.

System background is the base background color, it's pure white in light mode.

That's very familiar, and pure black in dark mode.

On top of that, there's secondary and tertiary system background colors, and these allow you to structure the information hierarchy of your app.

Note that dark mode is not just a simple inversion of light mode.

It's more subtle than that.

We use this idea of hierarchy for text as well.

There's four levels of text colors and they let you emphasize which elements are important relative to others.

For instance, the primary color might be used for things like titles.

The secondary color might be used for subtitles and so on.

The full catalog of colors is in the human interface guidelines, what they are and when to use them.

And we provide a lot of colors but you will need to make your own.

You can make your own dynamic colors and we'll show you how to do that a little later.

Now, let's move on to the second part of our design system - materials.

Here's an example in photos.

Materials are more than just colors.

There are blur effects that look like a translucent material on top of a background.

On top of that, there's vibrancy that cuts through the blur and stands out.

Of course these look good on top of any photo that we have, and they work in dark and in light mode.

The third part of the design system is the built-in views and controls provided by UIKit.

These are made using the same colors and the same materials.

Everything has been designed to fit together.

So, when you design your app for Dark Mode, before you touch a single line of code, learn this design system and figure out how to take advantage of what it provides.

Then, figure out what you'd like to customize.

iOS apps have so much diversity that we can't provide everything for you, but we do give you the power to do anything you need.

So, now that you've thought about your design, let's talk about how to implement it.

As soon as you build using the iOS 13 SDK, your app can appear in Light and Dark.

Switch the device to Dark Mode, run your app, and see what it looks like.

The first thing you'll find is that you do have work to do.

We can't do this for you automatically.

And ultimately it is your responsibility to make the choices to make your app look good in Dark Mode.

The first thing you'll need to do is start with colors.

Now in the past, each UI color had only one single value.

It was always the same.

Now they can be dynamic.

The color can have a different value in Light Mode and in Dark Mode.

And when you set one of these dynamic colors on a view, for instance, as a background color or a text color, UIKit will automatically use the correct value and will update when the mode changes.

So, you just set the color once.

If you're familiar with macOS Dark Mode, this is how NSColor works.

We're following the exact same pattern.

So, let's get started.

Let's implement Dark Mode in an app.

Over to Tyler for a demo.

[ Applause ]

Thanks, Kurt.

So, we have a simple demo app that we put together which was originally written against iOS 12, and we want to walk you through some of the changes that we'll make to update this to support Dark Mode in iOS 13.

This should hopefully give you an idea of some of the types of changes that you'll be making in your own apps.

So, here's our app running in a simulator on the right.

It's pretty simple.

So, why don't we just start by flipping it into Dark Mode and seeing how things look.

We can do that using the new environment overrides feature in Xcode.

So, we're running from Xcode here.

We can just apply a dark user interface dial override by toggling that switch.

And if you noticed, our app reacted right away.

It turned into Dark Mode.

Now, you might've noticed that for example the tab bar at the bottom of the screen changed.

And that's because we're using a UIKit standard tab bar, so we got all of that for free.

But of course our app didn't rally get very dark and that's because we have some work to do.

So, why don't we switch over and take a look at how our UI is built in the storyboard.

So, here's our home screen of our app.

And the first thing that we should probably take a look at is why we have this white background sitting behind everything.

We can select that view in our storyboard here.

And if you look in the attributes inspector on the right, you can see that we're setting a hard-coded white color.

That might've been fine before, but we now want to switch to using one of these new dynamic colors that Kurt talked about.

So, a great choice is the system background color.

So, you can find all these system colors in this new list here.

We'll choose system background.

And nothing actually changed when we did that, and that's because system background color is still white in light mode.

But what we can do is write in the storyboard editor, switch our preview to see what it's going to look like in Dark Mode.

So, if we do that, we can now see that we're getting a dark background color here.

But of course, our text just disappeared.

What happened?

Well, we have black text drawing on top of a black background.

So, we need to actually go in here and we can choose, let's start with the title label.

And we can see on the right that we have this configured also with a hard-coded black color for the text.

So, once again, we need to switch that to one of these new dynamic colors.

In this case, we have a great choice with the label color.

That's going to give us white in Dark Mode.

Similarly, we select our text view.

It's also set to black, so we'll need to update that as well.

So, now that we've made those changes, how about we run in the simulator and see how things are actually starting to look.

Here's our app running in light mode.

We'll once again go to those environment overrides and toggle it into dark.

And now we're seeing a lot more updating.

And so it's looking pretty good.

But what if we want to go a little bit further?

So, at the top we have this star image.

That's our app's logo.

And we want to customize that with a different color in Dark Mode.

So, this is easily possible.

We can select the image in our storyboard.

You'll see this is an image view.

It's configured with an image that comes from our asset catalog.

And this image is set up to render as a template image, which means that any tint color set on the image view is going to tint the image.

So, here we're tinting it with this custom logo color that we've already defined in our asset catalog.

Let's click this little arrow right here to jump straight to that color and see how it's set up.

So, here's that color.

It's this custom green color.

If we want to add a different appearance for Dark Mode, we can do that now by opening up a new slot specific for the dark appearance.

Once we've done that, we can select the dark appearance and change it, let's say to a nice yellow perhaps, something like that.

And now we have a color set up to automatically switch between two different appearances in light and dark.

So, if I save and switch back to our storyboard, you can see that as we toggle between light and dark in the preview, we're getting that automatic updating, which is great.

[ Applause ]

But there's more.

What if we want to actually update that background image?

So right now it's a nice tropical beach scene during the day.

Wouldn't it be cool if it kind of got to a nice sunset-darkened appearance in Dark Mode?

We can select that image view and do something really similar to what we did with the color.

So, here we have an image view, it's configured with this header image in our asset catalog.

We'll click the arrow to jump directly to that.

Here's where that image is set up.

And just like you saw with the color, it's really just the same thing.

You can open up a new slot for the dark appearance and then all we need to do is grab that sunset image, drag that in.

I'll save and just let's run for real and see what everything looks like in the simulator now.

So, this is light mode.

Nothing has changed.

It's all what we started with, even with all these changes.

But once we flip into Dark using our environment overrides, now everything on this screen has been updated to support Dark Mode.

[ Applause ]

So the really cool thing that you've seen here is that by using all of these dynamic colors and taking advantage of these dynamic images and custom colors in the asset catalog, we were able to get Dark Mode working without any code.

This is the advantage of taking advantage of all of this infrastructure that we provide for you in Xcode and UIKit.

You can also make these same sorts of changes to your app's Launch storyboard to support Dark Mode when your app is launching as well.

So, now why don't we take a look at another screen in our app.

If you tap this Learn More button, we present a view controller here in this new card-style presentation.

And of course we're going to want to make this support Dark Mode as well.

The interesting thing here is that on the bottom half, you can see we're taking advantage of some materials.

So, we have a blur over this background image and then we have a vibrant label sitting on top of that.

I'll hand things back over to Kurt to talk a little bit more about how you can use these visual effects to automatically update for Dark Mode as well.

Thank you, Tyler.

[ Applause ]

So, let me show some examples of these materials.

Here's some new blur effects.

There's thick, regular, thin, and ultra thin styles.

And of course these work in light and in dark.

Now here's how to use them.

Let's say in my app I have some views in the background and I want a blur on top of them.

First thing we do is create a UIBlur effect, and we specify a style.

In this case, we're going to use system material.

That's a good default choice.

Next, we create a UIVisual effect view using that effect.

We make sure its size and position are appropriate, then we put the visual effect view into our view hierarchy.

You see that it blurs everything underneath it.

It's pretty simple.

Now, on top of Blur effects, you can have vibrant content.

The vibrancy effect let's some of the background material pass through.

And in the past, there was only one style of vibrancy but now there are several.

There's four styles for text, three styles for larger filled areas, and even a style for thin separator lines.

Of course, these work in light and in dark.

So, here's how to implement vibrancy.

Now, vibrancy's always added on top of Blur, so, we're starting with the same setup from before.

We'll make a vibrancy effect and we specify a style.

In this case, I'm using fill because I'm going to show a larger filled area.

Note that when you create a vibrancy effect, you give it a reference to a Blur effect.

The two things operate together.

To show the vibrancy effect, we make another visual effect view, and then we'll put that inside the Blur effect.

Now, there's something a little subtle here, is that we don't ever add views directly as subviews of a visual effect view.

Instead, we go through its content view.

Now we'll add the views that become vibrant.

So again, I get the content view and then I add the views that will become vibrant and you see how it looks.

In this case, our view has an opaque background color.

But instead of its normal color, you see the vibrancy effect instead.

That's because with this vibrancy style, the color is ignored and only the alpha is used.

So, over to Tyler again to finish adding materials to our demo app.

[ Applause ]

Okay. So, we're back on this learn more screen and the difference between this screen and the last screen that we worked on is that this one is actually built entirely in code.

So, here we are in our view controller, and you can see that everything's assembled in this Load View method.

Before we actually get started, why don't we just try flipping on Dark Mode and seeing how things look.

We'll go here to our environment overrides, switch to Dark, and if you notice, there's actually a few things that changed.

We got the navigation bar this time for free, because that's coming from UIKit.

We also already have our dynamic custom color and the custom image set up, and it's the same ones that we updated before, so those are already working as well.

But of course, you can see we still have a white background and those materials don't look particularly great in the dark appearance here.

So, we're going to make some changes.

So, let's take a look at how all of this is built.

The first thing that we do is set up this view in the background.

This is where the white is coming from.

Just like you saw in the storyboard, we need to switch to use one of the new system background colors.

Let's choose system background again, just like we did before.

Next we're setting up the star image, and again, that's using our asset catalog color.

So, nothing to do there.

Here is the learn more label that you can see, the title of this screen.

We're going to need to make a change because we have the black hard-coded color here.

And once again, we can just switch to something like label.

Now, for the bottom half, what we have going on is an image view sitting at the very back.

That's right here.

Layered on top of the image view is a visual effect view configured with a Blur.

So, that's this light-style blur.

Then on top of that, we have another visual effect view configured with vibrancy.

That's right here.

And finally, inside of the content view of that vibrancy view, we have the label.

So, the changes that we need to make to update these materials are first of all to switch to a dynamic blur effect.

This light effect is not actually dynamic.

It's not going to update for us when we switch to Dark Mode.

So, we can take advantage of this brand-new system material palette that we have in iOS 13.

Great choice, because the system thin material for this particular use case, it'll look very similar to what we had before.

And then we also want to update our vibrancy to take advantage of the new system vibrancy styles.

And these vibrancy styles now come in a nice hierarchy.

So, we can add a style parameter.

And in this case it's kind of secondary content, so we can choose the secondary label and with those few changes, why don't we run and see how things look.

Let's bring up that presentation.

So, here we are in light mode.

It looks pretty similar to what we had before, but we'll now switch into Dark.

And bam. Everything has been updated with nice, new Dark Materials.

[ Applause ]

So, one point here is that even though we did write some code this time, there's nothing wrong with that, we didn't actually need to write any code that explicitly checked which mode we were in, or handled changes from Light to Dark Mode or vice versa.

This is also the advantage of taking of using these dynamic system colors and materials in your app.

I'll had it back over to Kurt to tell you a little bit more of how all of this works behind the scenes.

Thank you.

[ Applause ]

So, here's something subtle that you might not have noticed.

We used system background color as the main background in our app, that was black.

And then we used it again in that presented view.

And if you look carefully, you can see it's a lighter shade of grey.

That's because the design for Dark Mode has two different levels.

When the view fills the whole screen edge to edge, we call that the base level.

And then when content appears in a separate layer above that, we call that the elevated level.

So, in Dark Mode, the system-provided background colors have lighter values in the elevated level.

This helps distinguish them from the black background underneath.

But foreground colors don't change.

Now on iPhone, this can happen with presentations, as you see here.

On iPad, this can also happen when the app is in multitasking split view, so it doesn't fill the whole screen.

So, we've learned the essentials about implementing Dark Mode.

And we've both been saying that things just work.

It's just automatic.

So, let's dive deeper into how dynamic colors and images work.

And this will help you take advantage of all this power and flexibility.

So, we've seen how dynamic colors automatically resolve their appearance, but how does the color know whether it's light or dark?

This is done with trait collections.

So, each view and view controller in your app has a trait collection, and this helps determine the appearance of the views.

In this case, all of our views have the same trait collection.

The idiom is phone, because we're running on a phone and not an iPad or CarPlay.

The user interface style is dark, because we're in Dark Mode.

And the user interface level is base, because we're full screen.

So, if you want to find out what appearance to use, get the trait collection, we'll look at the user interface style, and also the other traits.

Now, dynamic colors are resolved using a trait collection.

So, it's the combination of a dynamic color and a trait collection that determine the final resolved color.

And normally this happens automatically but if you need resolve a color yourself, you can do that.

Let's say we have a dynamic color and we've gotten a trait collection from a view.

If we want to resolve that color, we just say dynamic color resolved color with trait collection.

It will return a fully resolved color that isn't dynamic anymore.

So, this is safe to call on any color.

If that color wasn't dynamic, it would've just returned itself.

You can also make custom dynamic colors.

We showed how to do that in the asset catalog, but you can do that in code as well.

Initialize a color with a closure, and the argument is a trait collection.

Then our closure will return another color.

So, each time this dynamic color needs to be resolved, that closure will be called with the appropriate trait collection.

And in the closure, you can use that trait collection to determine another color to return.

This example is implementing the diagram that we just saw.

So another question.

You might be wondering since dynamic colors can be used directly like any other color, how did they get resolved automatically?

If I have a dynamic color and I ask it for its RGB components, it'll return a result.

If I have black, I'll get 000.

Well, when I ask it, I don't pass in a Trait Collection.

How does it know?

Well, there's a new property on UITrait Collection called current, and this is set for you by UIKit.

And the color is resolved using that current Trait Collection.

And again, we're following the same pattern from the mac.

This is the same concept as NSAppearance.current.

Now, UIKit sets the current Trait Collection for you when it calls certain methods.

Here's an example.

I have a UIVIew Subclass and I've overridden the draw method.

So, this view will do all of its own drawing.

Before it calls this method, UIKit will set the current Trait Collection to the Views Trait Collection.

So that inside this code when a dynamic color needs to be resolved, it will use the Views Trait Collection.

In fact, when the mode changes, UIKit knows that you overrode the draw method.

So, it will automatically call set needs display on your view and will draw again with new colors.

UIKit also sets the current Trait Collection for you before it calls several other methods.

I'm subclasses of view, view controller, and presentation controller, explain draw.

But UIKit also sets the current Trait Collection during layout.

So, override layout subviews in your view subclass and then add code that resolves dynamic colors.

When the mode changes, set needs layout will be called.

Layout will happen again.

And this includes the corresponding calls on view controller and presentation controller.

Finally, all three kinds of these objects get trait collection did change, when a trait changes.

And views get tint color did change when the tint color changes.

So inside all of these methods, it's convenient to be able to use dynamic colors right away.

Now, here's a big point to keep in mind.

Outside of these methods, the current trait collection is not guaranteed to have any particular value, so if you need to resolve a dynamic color outside of these methods, you need to manage it.

Here's an example of why you might need to.

Lower-level classes, like CA Layer and CG Color, don't understand dynamic colors.

It's a UIKit concept.

So in this case, we're creating a layer and then setting its border color.

That takes a CG Color which can't be dynamic.

So, calling CG Color on a UIKit dynamic color needs to resolve it.

Now, let's imagine, we're not in one of those methods I mentioned earlier.

That means it's our responsibility to manage this process.

The first thing we need is a Trait Collection and we'll get it from a view.

Now we need to use that Trait Collection, and I'll show three ways to do this.

The first way we've already seen.

Ask the color to resolve itself using that Trait Collection.

Now this is fine if you have only a single color, but it can be awkward if you've got more.

And you have to remember to do it every time.

The second way is easier.

Just call perform as current on the Trait Collection.

That makes that Trait Collection become the current Trait Collection, and then it runs the code in the closure that you provide.

So, since we're resolving the color inside that closure, you get the right value.

Finally, the third option, directly set the current Trait Collection.

This looks a little intimidating but it's absolutely safe.

It's lightweight.

There are no side effects.

This is even safe to do on a background thread.

It will only affect the specific thread that you're running on, so it won't affect your main thread.

If you're going to do this, it's a good idea to save the current Trait Collection and then restore it afterwards in case any other code was relying on it.

And note that performance current does that for you, it does this exact same thing.

So, if you are doing this sort of thing, you will also want to know when the dynamic color needs to be resolved again.

That usually happens when traits change.

When traits change, Trait Collection did change as called.

But not all trait changes will affect colors.

If the user interface style changes from light to dark, that will obviously affect colors.

But if something like a size class changes because your app was resized, that won't affect colors.

So, it's best to use this method has different color appearance to see if the relevant traits have changed.

If it didn't, then you should resolve those dynamic colors again.

Finally, let's talk about images.

As you saw, you can create dynamic images in the asset catalog.

And when you show one of these images with UIImage view, it uses its Trait Collection to determine which image to display.

Now, UIImage view is doing the work here.

Unlike UIColor, UIImage doesn't pay attention to the current Trait Collection.

So, we recommend most of the time use UIImage view.

But if you need to resolve the color yourself, you can.

Given an image, just ask for its image asset.

That's that collection of all the different image variations.

Next, ask for a specific image that matches a specific Trait Collection.

You can also use the image asset to register new variations of an image at runtime.

So, if you draw your own image, you can add variations for light and dark.

Just put that in in an image view.

It will automatically show the right one.

So, let's talk about some things to keep in mind when adopting Dark Mode.

Over to Tyler.

[ Applause ]

Thanks, Kurt.

So, let's take a few minutes to review some more things about how Trait Collections work.

Because as you've seen, these play a really key role in Dark Mode.

The most important thing to remember about Trait Collections is that there is not just one in your entire app.

Trait Collections actually cascade through the hierarchy of your app, starting at the root level with the screen, then down to the window scene, which is new this year in iOS 13.

From there, down to the window.

At that point, traits start to flow into any presentations in your app and then the view controllers inside those presentations.

And finally, traits cascade through the view hierarchy inside each of your view controllers.

Because of this hierarchy, you always want to use the trait collection of the most specific view or view controller you can.

And when the value of a trait changes, you're going to receive trait collection did change on all of these objects inside of your app when they get that change.

So, if the system Dark Mode setting changes, for example, you'll see that trait change just cascade right through.

But I want to talk a little bit more about Trait Changes, because we have some new behavior this year in iOS 13.

Let's zoom into the bottom half of this diagram and walk through an example about what's changed.

So, here's a view that we want to add into the view hierarchy.

It's going to become the view inside of this view controller right above it.

The first thing that we'll do is create that view.

Now, when a view is being initialized, it hasn't yet been added inside of this trait hierarchy.

So, when the view's created, in iOS 13, UIKit is going to make a prediction about where it expects that view will end up, and populate that view's Trait Collection right from the start based on the predicted destination.

So, now let's add this view and yeah, it's pretty cool [laughter].

[ Applause ]

So, let's add this view into the hierarchy now.

So, we'll do that by calling out subview.

It moves over.

And once the view's actually added, it's going to inherit its actually traits from its parent trait environment, in this case the view controller above it.

But in this case, since the Trait Collection was correctly predicted up front, there wasn't a trait change that happened when that view moved into the view controller and view hierarchy.

So, what you've seen is some new behavior in iOS 13.

To recap, traits are now predicted during initialization.

View controllers and views will all receive a pretty complete Trait Collection right up front, which is really useful.

And then Trait Collection did change will only be called afterwards if any of those initial traits change.

So, when you're updating your existing apps for iOS 13, keep an eye out for existing code that you might have inside of methods like Trait Collection did change that expected the older behavior where there was often a trait change that occurred when you moved into the hierarchy.

Now, to make it a little bit easier to see when some of these trait changes are occurring in iOS 13, we have some helpful new debug logging this year.

You can enable using this launch argument and it'll tell you exactly when Trait Collection did change is called and the details of each of those changes.

It's really cool and you should give it a try.

Now, as you're updating your existing code or adding new code that uses traits, the best practice is to wait until layout to actually read the Trait Collection from the view or view controller and perform work based on it.

Traits are always updated before layout occurs.

And so if you get the Trait Collection from inside of one of these methods, you can rest assured that it's not going to be a prediction anymore.

It'll reflect the actual values that it was inheriting from its parent.

Just remember that if you put code in any of these layout methods, they can be called often when the view is visible.

Anytime something calls, a set needs layout.

So, be sure that you handle this by not repeating work unnecessarily.

So, that's what you should keep in mind when using Trait Collections.

But it turns out that we can also modify these trait collections inside of our app as well.

Here's an example of why you might want to do this.

Let's say that we want the learn more screen that we showed you in our app to always be dark, even when the rest of our app is in light appearance.

We can do this by taking advantage of the trait hierarchy.

So if this diagram represents our app, normally with the system in light mode, everything will have the light user interface style, just like you see here.

But if we just want let's say that one view controller on the bottom right and everything on the side of it to be dark, we can do that by overriding the user interface style trait with dark.

And now everything inside that view controller will be dark, even the rest of our app and even the system is running in light mode.

So, how do you actually do this override?

We have some new API this year in iOS 13 to make this really easy.

There are new properties on view controller and view so that you can just set the user interface style that you want and it'll apply to everything inside that and all of its children.

Now, you should use the view controller property whenever possible.

The view one should only be used if you don't have a view controller around for that particular thing you're trying to change.

And there are some caveats if you use that view property, so be sure to read the documentation if you do.

Finally, if your entire app should always just be light or dark, we have an info p list key you can set to make this really easy.

There's also some existing API that you can use to override traits.

This will let you override any trait on a view controller or presentation controller.

And the key thing to remember when you use this is that the override trait collection that you set should only contain values for the specific traits you care about overriding.

Leave all of the other traits unspecified - just don't touch them - so that UIKit will automatically fill in the normal inherited values for those traits.

That's pretty much all you need to know to successfully use Trait Collections in iOS 13.

Let's talk about some of the other API updates that we have this year and some other things to keep in mind as you're updating your apps to support Dark Mode.

And how about we'll start with the status bar.

Prior to iOS 13, we had two existing status bar styles.

We had the default style and light content.

New in iOS 13, we have a new dark content style, which is sort of taking over the place of what used to be default, and we've repurposed default to become an automatic style switching mode.

Now, the automatic switching is based on the user interface style of the view controller that controls the status bar appearance.

And as always, you can override a specific preferred status bar style on a view controller and recurrent a particular style if you desire.

Finally, it's not shown here but just a quick note that we also updated UIScroll view's indicator style to behave in a really similar way to this.

Next, let's talk about activity indicators.

In iOS 13, these existing styles are now deprecating.

And that's because they were implying a very specific color appearance, which doesn't really make sense anymore.

But in their place, we've introduced a handful of new styles, which are just based on the size of the activity indicator.

They also use a nice dynamic grey color by default, so they look great in light and dark mode without any changes.

But you can use the existing color property to set a custom, dynamic color, or even just a static color like white, if you want a particular fixed appearance.

Next, here are a few things to keep in mind when you're dealing with text in your app.

Now, by default, text classes like UILabel, UIText field, and UIText view use the label color.

So, if you're just setting the text on a new text field or text view, or a label, you'll just get a nice, correct appearance in both light and dark by default.

But if you use attributed strings in your app, and you set the attributed text of one of these text classes, or if you draw that attributed string yourself manually, you should keep in mind that you need to specify a foreground color.

Drawing an attributed string without any foreground color attribute like this, is actually defined to yield black text.

So, if you see black text in your app in Dark Mode, and you're using attributed strings, just make sure you're specifying a dynamic color like label, just as you see here for that foreground color attribute.

It's really quite easy.

Now, for those of you who have embedded web content in your app, you should know that Dark Mode is opt in.

You can do this by using the color scheme style property, or a meta tag with the same name.

And then you can use the prefers color scheme media query to actually customize different colors and images in both light and dark mode.

You can learn more about all these details in the Supporting Dark Mode in Web Content video from WWDC this year.

Now for those of you with tvOS apps, there's a good chance that you already support Dark Mode since we've had that available for a few years now.

Now, this year, your tvOS 13 apps will be expected to support Dark Mode by default, just like on iOS.

Most of the new API that we've covered today, such as all the dynamic colors and image assets, it's all available.

But a few things like the new system materials, are available on iOS only.

Now, if you do already support Dark Mode on your tvOS app, you can feel free to adopt all of these new features and new APIs anywhere that you'd like.

But if your existing Dark Mode implementation already is working well, you don't need to throw any of that away.

Now, if you'll be bringing your iPad app to the Mac this year, you can support Dark Mode on the Mac in the exact same way, using all the same API that we just talked about for iOS.

Now, your Mac app will follow the Dark Mode setting that the user selects in system preferences.

Very simple, just like on iOS.

But the only difference that you actually will notice is that in some cases, on the Mac, UIKit will automatically provide slightly different versions of dynamic colors for those system colors, and also some of the materials, to better match the ones that would normally be provided by a framework like AppKit on the Mac.

So, your app will look great right alongside other AppKit apps on the Mac as well.

Believe it or not, that's about all you need to know so that you can start updating all of your apps for Dark Mode in iOS 13 today.

Once you build against the iOS 13 SCK, your app will participate in Dark Mode by default.

But you might have some work to do, as you saw in our demos.

Now, we've made it really easy to take advantage of all of these new features and quickly get your app going with Dark Mode.

So be sure to start by using all of the new dynamic system colors and materials that we've made available this year.

And then of course, create your own custom appearances for custom colors, custom images, and more.

Finally, don't forget to take advantage of all of the powerful customizations and flexibility built into UIKit to make your apps really shine in Dark Mode.

We're really looking forward to seeing how all of your apps look in Dark Mode later this year.

For more information, we have some more content available and a little bit of sample code up on our session page.

And with that, thank you very much.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US