Xcode preview doesn't work with generic view - swift

I want to use generic subview in SwiftUI view.
struct UserChoiceView<DecisionView: View>: View {
let subview: DecisionView
var body: some View {
subview
.padding()
.offset(x: 10)
}
}
struct LikeDislikeView_Previews: PreviewProvider {
static var previews: some View {
UserChoiceView(subview: RoundedRectangle(cornerRadius: 10)
.fill(Color.red.opacity(0.9)))
}
}
Code above works fine, but Xcode can't generate preview.
I receive this error:
reference to generic type 'UserChoiceView' requires arguments in <...>
I think I can solve this by using AnyView type erasure, but maybe there are other workarounds.

You need to explicitly define the view in the preview.
struct UserChoiceView<DecisionView: View>: View {
let subview: DecisionView
var body: some View {
/// ...
}
}
struct LikeDislikeView_Previews: PreviewProvider {
static var decisionView: View {
// return SomeDecisionView()
}
static var previews: some View {
UserChoiceView<Self.decisionView>(subview: Self.decisionView)
}
}

Related

SwiftUI | Preview not updating on #Binding var value change

I am learning SwiftUI and tried to make a simple todo list but I'm having issues understanding why #Binding property doesn't update my preview.
The code is the following.
import SwiftUI
struct TodoRow: View {
#Binding var todo: Todo
var body: some View {
HStack {
Button(action: {
todo.completed.toggle()
}, label: {
Image(systemName: todo.completed ? "checkmark.square" : "square")
})
.buttonStyle(.plain)
Text(todo.title)
.strikethrough(todo.completed)
}
}
}
struct TodoRow_Previews: PreviewProvider {
static var previews: some View {
TodoRow(todo: .constant(Todo.sampleData[0]))
}
}
The preview doesn't update when I click the square button but the app works fine. Am I using it incorrectly?
EDIT:
Even without .constant(#), the preview doesn't work.
struct TodoRow_Previews: PreviewProvider {
#State private static var todo = Todo.sampleData[0]
static var previews: some View {
TodoRow(todo: $todo)
}
}
Found a solution in the article Testing SwiftUI Bindings in Xcode Previews.
In order for previews to change you must create a container view that holds state and wraps the view you're working on.
In my case what I've ended up doing was changing my preview to the following.
struct TodoRow_Previews: PreviewProvider {
// A View that simply wraps the real view we're working on
// Its only purpose is to hold state
struct TodoRowContainer: View {
#State private var todo = Todo.sampleData[0]
var body: some View {
TodoRow(todo: $todo)
}
}
static var previews: some View {
Group {
TodoRow(todo: .constant(Todo.sampleData[0]))
.previewDisplayName("Immutable Row")
TodoRowContainer()
.previewDisplayName("Mutable Row")
}
}
}

How to fix TextField popping back to root on cancel?

I have a simple test watchOS application with a TextField in a secondary view (navigated to from a NavigationLink).
However, when the TextField is canceled or submitted, it will pop back out to the root view instead of staying in the current view. I can't find any information on this anywhere else. Any fixes?
ContentView:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
NavigationLink("what", destination: DestinationView())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DestinationView:
import SwiftUI
struct DestinationView: View {
#State private var message: String = ""
var body: some View {
TextField(
"Send Something...",
text: $message
)
}
}
struct DestinationView_Previews: PreviewProvider {
static var previews: some View {
DestinationView()
}
}
I found the issue..
I was using a NavigationView, which is deprecated. I removed it and now it's working as intended. (XCode 13.2.1, watchOS 8.3)
*facepalm*

SwiftUI Preview not working with Binding<Bool> [duplicate]

I present this view as a sheet from its parent view
struct NamesView: View {
#Binding var match: Match
var body: some View {
...
}
}
Since the match source of truth is in the parent view presenting this NamesView sheet, when the view is constructed I pass in a $match binding and data flows as intended.
However, when constructing this view in a preview provider
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView()
}
}
the compiler says that NamesView() expects a match argument of type Binding<Match> (Match being the parent view presenting this view as a sheet). I'm not sure what would be a good way to proceed from here or if this is a limitation of SwiftUI.
If you want only constant preview, then it can be
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match: .constant(Match()))
}
}
if you want it in live, the it can be
struct NamesView_Previews: PreviewProvider {
struct BindingTestHolder: View {
#State private var testedMatch = Match()
var body: some View {
NamesView(match: $testedMatch)
}
}
static var previews: some View {
BindingTestHolder()
}
}
Try this:
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match:.constant(Match()))
}
}
I wrote about this in depth here, but the short version is simply to add the following extension:
extension Binding {
public static func variable(_ value: Value) -> Binding<Value> {
var state = value
return Binding<Value> {
state
} set: {
state = $0
}
}
}
And then do...
struct NamesView_Previews : PreviewProvider {
static var previews: some View {
NamesView(match: .variable(Match()))
}
}
This lets you actually mutate the value, useful when doing live previews in Xcode 14.

Add Sample View to PreviewProvider for Custom Wrapper View

I have created a "wrapper" view to handle some standard branding like background color, etc which takes a View as a parameter, but I can't figure out how to pass a sample View in PreviewProvider to see it live in Xcode. This wrapper view works when I build it in the simulator - I just can't figure out how to preview it.
Wrapper View
import SwiftUI
struct BackgroundView<Content: View>: View {
let contentView: Content
init(#ViewBuilder content: #escaping () -> Content) {
self.contentView = content()
}
var body: some View {
ZStack {
GeometryReader { geo in
HStack {
Spacer()
Image("Background Watermark")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 0.7 * geo.size.width, height: geo.size.height, alignment: .topLeading)
}
}
VStack {
self.contentView
}
}
.background(Color("Purple"))
.edgesIgnoringSafeArea(.top)
}
}
// This is where I'm struggling
struct BackgroundView_Previews: PreviewProvider {
static var previews: some View {
BackgroundView(content: ... )
}
}
What I've Tried
I tried passing a simple Text to it, but get Cannot convert value of type 'Text' to expected argument type '() -> Content'
static var previews: some View {
BackgroundView(content: Text("Hello, world"))
}
Then I tried passing a function that returns some View thinking the closure was the issue, but I get Instance member 'sampleTextView' cannot be used on type 'BackgroundView_Previews':
func sampleTextView() -> some View {
Text("Hello world")
}
static var previews: some View {
BackgroundView(content: sampleTextView)
}
Any idea what I'm doing wrong or how to load up a simple Text view just to be able to preview this?
It is expected to be closure, like
static var previews: some View {
BackgroundView(content: { Text("Hello, world") })
}
or
static var previews: some View {
BackgroundView {
Text("Hello, world")
}
}

Can I make a protocol that inherits from 'View' to display a specific 'View'?

I'm trying to make some SwiftUI-Views with similar properties. So I want to make a protocol for them and display an instance of this protocol.
protocol SpecialView: View { ... }
struct SpecialViewA : View, SpecialView {
...
var body: some View {
Text("Hello World!")
}
}
struct ContentView: View {
var currentlyDisplayedView: some SpecialView
var body: some View{
currentlyDisplayedView
}
}
//in preview:
ContentView(SpecialViewA())
I expect the ContentView to accept my SpecialViewA as a SpecialView. However, in the preview I get
"Cannot convert value of type 'SpecialViewA' to expected argument type 'some SpecialView'".
and when trying to display I get:
"[...] requires that 'some SpecialView' conform to 'View'"
What am I doing wrong? Is there an easier way?
You were close..., but this will compile:
protocol SpecialView: View {
}
struct SpecialViewA : View, SpecialView {
var body: some View {
Text("Hello World!")
}
}
struct ContentView<V>: View where V: SpecialView {
var currentlyDisplayedView: V
var body: some View{
currentlyDisplayedView
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(currentlyDisplayedView: SpecialViewA())
}
}
#endif