Introducing and explaining the PreviewBackground package

While learning and experimenting with SwiftUI, I use the canvas assistant editor to preview SwiftUI views extensively. It is an amazing feature of Xcode 11 and I love it. There is a quirk that gets difficult for me though – the default behavior of the preview provider uses a gray background. I frequently use multiple previews while making SwiftUI elements, wanting to see my creation on a background supporting both light and dark modes.

The following little stanza is a lovely way to iterate through the modes and displaying them as previews:

#if DEBUG
struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ForEach(ColorScheme.allCases,
                    id: \.self) { scheme in

                Text("preview")
                    .environment(\.colorScheme, scheme)
                    .frame(width: 100,
                           height: 100,
                           alignment: .center)
                    .previewDisplayName("\(scheme)")
            }
        }
    }
}
#endif

Results in the following preview:

The gray background doesn’t help all that much here. It is perfect when you are viewing a fairly composed element set, as you are often working over an existing background. When you are creating an element to stand alone, or moving an element. In those cases, I really want a background for the element.

And this is exactly what PreviewBackground provides. I made PreviewBackground into a SwiftPM package. While I could have created this effect with a ViewModifier, I tried it out as a ViewBuilder instead, thinking it would be nice to wrap the elements I want to preview explicitly.

The same example, using PreviewBackground:

import PreviewBackground

#if DEBUG
struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ForEach(ColorScheme.allCases,
                    id: \.self) { scheme in
                PreviewBackground {
                    Text("preview")
                }
                .environment(\.colorScheme, scheme)
                .frame(width: 100,
                       height: 100,
                       alignment: .center)
                .previewDisplayName("\(scheme)")
            }
        }
    }
}
#endif

The code is available on Github, and you may include it within your own projects by adding a swift package with the URL: https://github.com/heckj/PreviewBackground

Remember to import PreviewBackground in the views where you want to use it, and work away!

Explaining the code

There are not many examples of using ViewBuilder to construct a view, and this is a simple use case. Here is how it works:

import SwiftUI

public struct PreviewBackground<Content>: View where Content: View {
    @Environment(\.colorScheme) public var colorSchemeMode

    public let content: () -> Content

    public init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    public var body: some View {
        ZStack {
            if colorSchemeMode == .dark {
                Color.black
            } else {
                Color.white
            }
            content()
        }
    }
}

The heart of using ViewBuilder is using it within a View initializer to return a (specific but) generic instance of View, and using the returned closure as a property that you execute when composing a view.

There is a lot of complexity in that statement. Allow me to try and explain it:

Normally when creating a SwiftUI view, you create a struct that conforms to the View protocol. This is written in code as struct SomeView: View. You may use the default initializer that swift creates for you, or you can write your own – often to set properties on your view. ViewBuilder allows you to take a function in that initializer that returns an arbitrary View. But since the kind of view is arbitrary, we need to make the struct generic – since we can’t assert exactly what type it will be until the closure is compiled. To tell the compiler it’ll need to do the work to figure out the types, we label the struct as a being generic, using the <SomeType> syntax:

struct SomeView<Content>: View where Content: View

This says there is a generic type that we’re calling Content, and that generic type is expected to conform to the View protocol. There is a more compact way to represent this that you may prefer:

struct SomeView<Content: View>: View

Within the view itself, we have a property – which we name content. The type of this content isn’t known up front – it is the arbitrary type that the compiler gets to infer from the closure that will provided in the future. This declaration is saying the content property will be a closure – taking no parameters – that returns some an arbitrary type we are calling Content.

public let content: () -> Content

Then in the initializer, we use ViewBuilder:

public init(@ViewBuilder content: @escaping () -> Content) {
    self.content = content
}

In case it wasn’t obvious, ViewBuilder is a function builder, the swift feature that is enabling this declarative structure with SwiftUI. This is what allows us to ultimately use it with in that declarative syntax form.

The final bit of code to describe is using the @Environment property wrapper.

@Environment(\.colorScheme) public var colorSchemeMode

The property wrapper is not in common use, but perfect for this need. The property wrapper uses exposes a specific part of the existing environment as a local property for this view. This is what enables PreviewBackground to choose the color for the background appropriate to the mode. By reading the environment it chooses an appropriately colored background. It then uses that property to assemble a view by invoking the property named content (which was provided by the function builder) within a ZStack.

By using ViewBuilder, we can use the PreviewBackground struct like any other composed view within SwiftUI:

var body: some View {
    PreviewBackground {
        Text("Hello there!")
    }
}

If we had created this code as a ViewModifier, then using it would look different – instead of the curly-bracket syntax, we would be chaining on a method. The default set up for something like that looks like:

var body: some View {
    Text("Hello there!")
    .modify(PreviewBackground())
}

I wanted to enable the curly-bracket syntax for this, hence the choice of using a ViewBuilder.

A side note about moving code into a Swift package

When I created this code, I did so within the context of another project. I wanted to use it across a second project, and the code was simple enough (a single file) to copy/paste – but instead I went ahead and made it a Swift package. Partially to make it easier for anyone else to use, but also just to get a bit more experience with what it takes to set up and use this kind of thing.

The mistake that I made immediately on moving the code was not explicitly making all the structs and properties public. It moved over, compiled fine, and everything was looking great as a package, but then when I went to use it – I got some really odd errors:

Cannot call value of non-function type 'module<PreviewBackground>'

In other instances (yes, I admit this wasn’t the first time I made this mistake – and it likely won’t be the last) the swift compiler would complain about the scope of a function, letting me know that it was using the default internal scope, and was not available. But SwiftUI and this lovely function builder mechanism is making the compiler work quite a bit more, and it is not nearly as good at identifying why this mistake might have happened, only that it was failing.

If you hit the error Cannot call value of non-function type when moving code into a package, you may have forgotten to make the struct (and relevant properties) explicitly public.

Published by heckj

Developer, author, and life-long student. Writes online at https://rhonabwy.com/.

%d bloggers like this: