24

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?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Simon
  • 6,413
  • 6
  • 34
  • 57
  • When you context-click the play button next to the preview and select "Debug Preview", you get a debug session and can use Environment overrides and other debugging features against your SwiftUI preview. – wolfrevo Jun 10 '19 at 16:22
  • 3
    Does this answer your question? [Dark mode in SwiftUI Preview doesn't have a dark background with Xcode 11.4](https://stackoverflow.com/questions/60912203/dark-mode-in-swiftui-preview-doesnt-have-a-dark-background-with-xcode-11-4) – Curiosity Jun 08 '20 at 05:41

3 Answers3

33

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.

Ky -
  • 30,724
  • 51
  • 192
  • 308
Matthew Price
  • 496
  • 4
  • 4
  • 1
    FYI: I had to add a "Group" around the two ContentViews() for the last one to work. – EricS Jun 10 '19 at 01:00
  • 3
    Is this actually working for anyone? I'm having a hell of a time getting dark mode to work in Beta1. Even my modes in the color assets catalog isn't working when I manually flip to dark mode while running the simulator. Of course, all the default apple controls work... – Craig Fisher Jun 10 '19 at 06:25
  • Check this if your preview is not displaying dark mode https://stackoverflow.com/a/56593027/3815069 – M Reza Jun 15 '19 at 06:22
  • how to check whether color scheme is dark or light programmatically? it is because i need to change my image when it is dark – Khant Thu Linn Jun 17 '19 at 02:11
14

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.

Jakub Charvát
  • 331
  • 3
  • 9
6

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

DoesData
  • 6,594
  • 3
  • 39
  • 62
  • 1
    The issue is still here in Xcode 11.3.1. Works fine in the simulator without the `NavigationView` though... – Jens Willy Johannsen Feb 28 '20 at 10:19
  • It's a shame this still isn't fixed. Also if you set previewLayout to fixed, it shows the navigationview, and attempting to hiding it won't work. We'll have to put up with this workaround and any flaws it comes with until apple fixes it – pizzae May 09 '20 at 14:23