I snagged this graphic of Property Wrappers from Karin Prater's Video:
Property wrappers in SwiftUI: How to choose between @EnvironmentObject, @ObservableObject and more
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.
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.
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.
The PodcasterView shows the technique of binding to the isFavorite property inside the Episode class using: $episode.isFavorite varible.
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.
See Also: