Tiny Hints: a couple of Xcode 11.3 SwiftUI Preview tips

Xcode 11.3 & SwiftUI Previews: Dark Mode preview bug work-around, compact previews, multi-previews for ColorSchemes and Dynamic Text sizes.

Few small tips for previewing with SwiftUI that I just figured out.

Here’s the simple test ContentView.swift file to use in a standard single view iOS SwiftUI app template project in Xcode 11.3 that we’ll use to explore these tips:

import SwiftUI

struct ContentView: View {
  var body: some View {
    HStack {
      Image(systemName: "paperplane")
        .font(.largeTitle)
      VStack {
        Text("Title Text")
          .font(.title)
        Text("Subtitle goes here")
          .font(.subheadline)
      }
    }
  }
}

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Standard Preview looks like this:

So, the first thing is WOW that takes a lot of space for just a small horizontal cell I’m working on (like for list row or whatever). It’s especially annoying if you’re trying to make sure it works well in dark mode, at different dynamic type sizes, etc. First tip is how to make that work better.

1 – How to preview a horizontal or smaller view in less space in the Xcode Canvas

Context: you’re working on a custom horizontal control, or a row in a list and you want to do multiple previews for light & dark mode, or different text size classes.

By default each preview gets an entire iPhone worth of chrome around it which means you have to scroll to check all your variations out – tiresome! Avoid this by using the .previewLayout modifier with a size that’s sufficient for your view. For example:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

This produces a much more manageable preview like this:

The dimensions you use in .previewLayoutwill be specific to whatever you’re working on, of course, but this comes in really handy when you are previewing multiple versions of a horizontal view like this. Let’s demonstrate that with our next tip by making Dark Mode preview in Xcode 11.3 usable outside a NavigationView.

2 – Dark Mode Preview only seems to really work in NavigationViews

So the standard advice on how to preview in Dark Mode is to add .environment(.colorScheme, .dark) to the end of your view creation in PreviewProvider like so:

struct PreviewsDarkNotDark: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
      .border(Color.green)
      .environment(\.colorScheme, .dark)
  }
}

To make it clear what’s going on I’ve added a green border modifier to our ContentView so that we can see where it is when previewed like this because otherwise we’d wonder if it even exists!

Kind of useless :-/

Our views are using the colorScheme that was set and are rendering as if the device is in dark mode, but we can’t see it on a white background. So let’s fix that:

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ZStack {
      Color(.black)
      ContentView()
    }
    .previewLayout(.fixed(width: 400, height: 100))
    .environment(\.colorScheme, .dark)
  }
}

Which looks a lot better:

3 – Combining previews and reducing duplicate code

So now we have dark mode looking good so let’s combine the preceding two tips and look a both light and dark modes on the same screen and without having to scroll:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ZStack {
      Color(.black)
      ContentView()
    }
    .previewLayout(.fixed(width: 400, height: 100))
    .environment(\.colorScheme, .dark)
  }
}

Which looks like this:

Looking pretty good! While writing them in separate PreviewProviders like that helpfully uses their struct names to label them, it does involve a bit of duplicated code. Here’s an alternate way to do it with a ViewModifier to put shared modifiers in and a Group of views which Xcode makes each their own Preview:

struct MyPreviewModifer: ViewModifier {
  var displayMode: ColorScheme = .light
  var name: String = ""
  
  func body(content: Content) -> some View {
    ZStack {
      Color(displayMode == .light ? .clear : .black)
      content
    }
    .previewLayout(.fixed(width: 400, height: 120))
    .previewDisplayName(name)
    .environment(\.colorScheme, displayMode)
  }
}


struct PreviewsLightDarkDynamicText: PreviewProvider {
  static var previews: some View {
    Group {
      ContentView()
        .modifier(MyPreviewModifer())
      
      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark))

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark,
                        name: "dark extraSmall"))
        .environment(\.sizeCategory, .extraSmall)

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark, 
                        name: "dark extraExtraLarge"))
        .environment(\.sizeCategory, .extraExtraLarge)
    }
  }
}

This gives us four previews and adds the ability to name them since they’re all in the same PreviewProvider struct and so don’t get named individually any longer. Looks like this now:

Well, this “tiny tips” got a little long… but hopefully you found these small tips to be useful.

(Code in gist here for easier copy and paste)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s