Xcode 11 - SwiftUI Preview Dark Mode

Viewed 8715

In Xcode 11, we can enable dark mode when the app is running by toggling the Environment Overrides at the bottom of the debug area like so.

Environment Overrides

SwiftUI has the Canvas editor which generates live previews of the app as you are building your interface.

Is there a way where we can toggle to dark mode in these previews?

3 Answers

You should have something like this at the bottom of the file that's being previewed. This is what Xcode uses to generate the preview:

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

To change the preview to dark mode, you just need to specify a colorScheme:

static var previews: some View {
    ContentView().colorScheme(.dark)
}

Or, you can even chose to preview light and dark mode at the same time:

static var previews: some View {
    Group {
        ContentView().colorScheme(.light)
        ContentView().colorScheme(.dark)
    }
}

I recommend watching the Introducing SwiftUI session for more examples of SwiftUI and how powerful the previews can be.

TLDR:

Just add the .background(Color(UIColor.systemBackground)) and .environment(\.colorScheme, .dark) modifiers to the preview. For an explanation, examples, some modifications and a few tips to make it prettier and even simpler, please read the whole answer.

Explanation

I know this question is fairly old, but I've found a way that isn't too painful to implement and doesn't require any wrapping in a NavigationView. Furthermore, it also retains the correct behaviour of .previewLayout(.sizeThatFits).

Essentially, when you define a struct that conforms to PreviewProvider, you are only defining the content, but the background of the preview is managed for you by Xcode. Therefore, applying .environment(\.colorScheme, .dark) only changes the actual View to dark mode, but not the background. The reason NavigationView solves this issue is fairly simple - it adds a background to the view covering all of the preview's white background.

The fix itself is also fairly simple - all that you need to do is to add a background to your view in the preview. So for a simple view like this:

struct ExampleView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

And a set of previews like this:

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ExampleView()
            ExampleView()
                .environment(\.colorScheme, .dark)
        }.previewLayout(.sizeThatFits)
    }
}

You get an output that looks like this:

Light and dark mode rendering of ExampleView above

In order to make the second preview appear on a dark background, add it by calling .background(Color(UIColor.systemBackground)) on the View:

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ExampleView()
            ExampleView()
                .background(Color(UIColor.systemBackground))
                .environment(\.colorScheme, .dark)
        }.previewLayout(.sizeThatFits)
    }
}

And you get two previews that look like this:

Light and dark mode rendering of ExampleView above with background fixes

Extra options

There are several modifications you could make. Firstly, depending on the layer the cell will be on you can replace UIColor.systemBackground with UIColor.secondarySystemBackground or UIColor.tertiarySystemBackground. Read more about dynamic system colours in the human interface guidelines or the UI Element Colors portion of the UIColor developer documentation.

Lastly, if you're going to be using this often and don't want to write out the whole call to UIColor every time, it might be a good idea to create an extension on Color and define them as static variables there:

extension Color {
    static let systemBackground = Color(UIColor.systemBackground)
    static let secondarySystemBackground = Color(UIColor.secondarySystemBackground)
    static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground)
}

Then you can replace your calls to Color(UIColor.systemBackground) with a much nicer Color.systemBackground.

Note: At the time of writing, you need a NavigationView as your top-level view for .environment(.colorScheme, .dark) to work. But then the (large) navigation bar covers the color blocks, so the two navigationBar modifiers make the bar smaller and hide it ... sort of. This might be a bug in Xcode.

Source - paid content

I tested this on Xcode 11.2.1 and the issue with the NavigationView still exists. The environment doesn't seem to change unless your entire view is wrapped in a NavigationView. You can try to hide the NavigationView using .navigationBarTitle("") & .navigationBarHidden(true).

Example:

struct ContentView: View {
    var body: some View {
        NavigationView {
            Text("Light vs Dark Mode")

            // Uncomment these lines if you don't want the navigation bar
            // .navigationBarTitle("")
            // .navigationBarHidden(true)

            // You can also apply a colorScheme here
            // which will impact how the view looks when the app
            // is launched on device. Regardless of the users theme settings
        }// .environment(\.colorScheme, .dark)
    }
}

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

        // If you want you can display both schemes in a group
        Group {
            ContentView()
            .environment(\.colorScheme, .light)

           ContentView()
           .environment(\.colorScheme, .dark)
       }
    }
}

Sample preview in dark mode:

enter image description here

Related