Swift Property Wrappers and UI Previews

I have to admit it... I don't understand Swift Property Wrappers

It would appear that using a Swift property wrapper should solve more than a few problems. However, just a few minutes later and I'm fighting with the compiler over passing data to the UI Preview and getting more and more frustrated.

I don't seem to be the only one with this issue. A quick search of the google and we have lots of articles - some even appear to work.

apple swift property wrapper in preview static

But I don't yet... have a general solution - a concept of the solution... no working model. Can you point me to one?


Let's start with Apple's code in the Developer Documentation.

Managing user interface state

Encapsulate view-specific data within your app’s view hierarchy to make your views reusable.


The PlayButton code at the left has a @Binding variable isPlaying. The preview code will not compile until you pass in this variable upon calling the PlayButton() view. So one technique I've learned is PlayButton(isPlaying: .constant(false)). You might say this works, but the view can NOT change the state and therfore you cannot preview the not-playing icon.

Without the .constant( ) one gets the compiler all upity with a 'Cannot convert value of type 'Bool' to expeccted argument type 'Binding<Bool>' error.

So this is our first example of a property wrapper making the UI Preview code more complex. Any ideas on the Best of Class code for this?

One idea is to put the @State var in the Preview struct and then pass the state var with the $ as in $isPlaying. But that results in the Error: Instance member '$isPlaying' cannot be used on type 'PlayButton_Previews'.

We can fix the Instance member usage issue with a static var - but again we lose the ability to toggle the boolean value. Jim Dovey may have a solution with his StatefulPreviewWrapper see below - How to preview a custom View that takes bindings as inputs. But a wrapper to undo the wrapping of a binding variable - seems too complex.

Sample data passed in...

Let's move along and look at PlayerView. An example of passing a static "preview" object in the UI Preview.

In this example it is very easy to setup
mock-example data for the purpose of UI previews. They just have to be static properties - like Episode.preview. I like the name "preview" sometimes I will use "example" for this class data.

Binding to a property inside an object

The PodcasterView shows the technique of binding to the isFavorite property inside the Episode class using: $episode.isFavorite varible.


Now let's try the
Environment

SwiftUI can make passing parameters down the View hierarrchy easy with EnvironmentObjects. But making the Preview play nicely with the Environment is a bit tricky. If you get a preview diagnostic error: No ObservableObject... found. Try adding a @State object.

[Using example code in @EnvironmentObject explained for sharing data between views in SwiftUI]


Your build will compile - the Preview will fail to execute with an error: Instance member cannot be used...

Next you will need to add a static keyword to that @StateObject inside the Preview.


Be careful of the proper usage of @State... it is for simple value types (NOT reference classes); see: SwiftUI: @State vs @StateObject vs @ObservedObject vs @EnvironmentObject article for a deeper explaination.

I like Karin Prater's graphic at the top, it points out a secret - if you have a complex object - you should be using the property wrappers that end with Object (e.g. @StateObject), the simple primative types (Bool, String, Int) can be wrapped with the left handside of the diagram.

One result of this article may be that I've arrived at a way of making the Previews work with Property Wrappers... use a local @State / @StateObject wrapper and insert the
static keyword to the parameter needed for the preview.