SwiftUI Previews struct "Missing argument for parameter 'text' in call" [duplicate] - ios

With SwiftUI (Xcode 11.1), I've got some Views set up with 2-way bindings (using #Binding). Two-way updating works great.
However, how can I instantiate the view from the PreviewProvider?
For example:
struct AddProjectView: View {
#Binding public var showModal: Bool
var body: some View {
return VStack {
Text("Add Project View")
Button("Dismiss") {
self.showModal = false
}
}
}
}
I can't do this, because "true" is not a Binding:
struct AddProjectView_Previews: PreviewProvider {
static var previews: some View {
AddProjectView(showModal: true)
}
}
And I can't do this because "Property wrappers are not yet supported on local properties":
struct AddProjectView_Previews: PreviewProvider {
static var previews: some View {
#Binding var show = true
return AddProjectView(showModal: $show)
}
}
How do we do this?
Thanks!!

.constant is meant exactly for that:
/// Creates a binding with an immutable value.
struct AddProjectView: View {
#Binding public var showModal: Bool
var body: some View {
return VStack {
Text("Add Project View")
Button("Dismiss") {
self.showModal = false
}
}
}
}
struct AddProjectView_Previews: PreviewProvider {
static var previews: some View {
AddProjectView(showModal: .constant(true))
}
}

If you only need a constant value, use .constant(VALUE):
struct YourView_Previews: PreviewProvider {
static var previews: some View {
YourView(yourBindingVar: .constant(true))
}
}
If you need a value that can be changed in the live preview, I like to use this helper class:
struct BindingProvider<StateT, Content: View>: View {
#State private var state: StateT
private var content: (_ binding: Binding<StateT>) -> Content
init(_ initialState: StateT, #ViewBuilder content: #escaping (_ binding: Binding<StateT>) -> Content) {
self.content = content
self._state = State(initialValue: initialState)
}
var body: some View {
self.content($state)
}
}
Use it like so:
struct YourView_Previews: PreviewProvider {
static var previews: some View {
BindingProvider(false) { binding in
YourView(yourBindingVar: binding)
}
}
}
This allows you to test changing the binding in the live preview.

You have to declare it as #State on your Preview.
struct AddProjectView_Previews: PreviewProvider {
#State static var showModal: Bool = false
static var previews: some View {
AddProjectView(showModal: $showModal)
}
}
Also remeber that it needs to be static as it is used in a static func.

Related

How to properly implement a global variable in SwiftUI

I am going to create a SwiftUI application where I want to be able to swap between 3 modes. I am trying EnvironmentObject without success. I am able to change the view displayed locally, but from another View (in the end will be a class) I get a
fatal error: No ObservableObject of type DisplayView found. A View.environmentObject(_:) for DisplayView may be missing as an ancestor of this view.
Here is my code. The first line of the ContentView if/else fails.
enum ViewMode {
case Connect, Loading, ModeSelection
}
class DisplayView: ObservableObject {
#Published var displayMode: ViewMode = .Connect
}
struct ContentView: View {
#EnvironmentObject var viewMode: DisplayView
var body: some View {
VStack {
if viewMode.displayMode == .Connect {
ConnectView()
} else if viewMode.displayMode == .Loading {
LoadingView()
} else if viewMode.displayMode == .ModeSelection {
ModeSelectView()
} else {
Text("Error.")
}
TestView() //Want this to update the var & change UI.
}
.environmentObject(viewMode)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(DisplayView())
}
}
//FAILS
struct TestView: View {
#EnvironmentObject var showView: DisplayView
var body: some View {
HStack {
Button("-> load") {
self.showView.displayMode = .Loading
}
}
}
}
struct ConnectView: View {
var body: some View {
Text("Connect...")
}
}
struct LoadingView: View {
var body: some View {
Text("Loading...")
}
}
struct ModeSelectView: View {
var body: some View {
Text("Select Mode")
}
}
I would like to be able to update DisplayView from anywhere and have the ContentView UI adapt accordingly. I can update from within ContentView but I want to be able update from anywhere and have my view change.
I needed to inject BEFORE - so this fixed things up:
#main
struct fooApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(DisplayView()) //super key!
}
}
}
I also tried a Singleton class to store some properties - and thus they are available from anywhere and can be updated anywhere - without having to declare EnvironmentObject. It's just another way that can work in different circumstances.
class PropContainerModel {
public var foo = "Hello"
static let shared = PropContainerModel()
private override init(){}
}
And then somewhere else
let thisFoo = PropContainerModel.shared.foo
//
PropContainerModel.shared.foo = "There"
Update here (Singleton but changes reflect in the SwiftUI UI).
class PropContainerModel: ObservableObject
{
#Published var foo: String = "Foo"
static let shared = PropContainerModel()
private init(){}
}
struct ContentView: View
{
#ObservedObject var propertyModel = PropContainerModel.shared
var body: some View {
VStack {
Text("foo = \(propertyModel.foo)")
.padding()
Button {
tapped(value: "Car")
} label: {
Image(systemName:"car")
.font(.system(size: 24))
.foregroundColor(.black)
}
Spacer()
.frame(height:20)
Button {
tapped(value: "Star")
} label: {
Image(systemName:"star")
.font(.system(size: 24))
.foregroundColor(.black)
}
}
}
func tapped(value: String)
{
PropContainerModel.shared.foo = value
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

cannot find $exercisePlan in scope (SwiftUI) [duplicate]

How do I generate a preview provider for a view which has a binding property?
struct AddContainer: View {
#Binding var isShowingAddContainer: Bool
var body: some View {
Button(action: {
self.isShowingAddContainer = false
}) {
Text("Pop")
}
}
}
struct AddContainer_Previews: PreviewProvider {
static var previews: some View {
// ERROR HERE <<<-----
AddContainer(isShowingAddContainer: Binding<Bool>()
}
}
In Code above, How to pass a Binding<Bool> property in an initialiser of a view?
Just create a local static var, mark it as #State and pass it as a Binding $
struct AddContainer_Previews: PreviewProvider {
#State static var isShowing = false
static var previews: some View {
AddContainer(isShowingAddContainer: $isShowing)
}
}
If you want to watch the binding:
Both other solutions [the "static var" variant AND the "constant(.false)"-variant work for just seeing a preview that is static. But you cannot not see/watch the changes of the value intended by the button action, because you get only a static preview with this solutions.
If you want to really watch (in the life preview) this changing, you could easily implement a nested view within the PreviewProvider, to - let's say - simulate the binding over two places (in one preview).
import SwiftUI
struct BoolButtonView: View {
#Binding var boolValue : Bool
var body: some View {
VStack {
Text("The boolValue in BoolButtonView = \(boolValue.string)")
.multilineTextAlignment(.center)
.padding()
Button("Toggle me") {
boolValue.toggle()
}
.padding()
}
}
}
struct BoolButtonView_Previews: PreviewProvider {
// we show the simulated view, not the BoolButtonView itself
static var previews: some View {
OtherView()
.preferredColorScheme(.dark)
}
// nested OTHER VIEW providing the one value for binding makes the trick
struct OtherView : View {
#State var providedValue : Bool = false
var body: some View {
BoolButtonView(boolValue: $providedValue)
}
}
}
Other way
struct AddContainer_Previews: PreviewProvider {
static var previews: some View {
AddContainer(isShowingAddContainer: .constant(false))
}
}

How to use #State if #Binding not provided in the initializer

Imagine a view with some #Binding variables:
init(isEditing: Binding<Bool>, text: Binding<Bool>)
How can we have the selection working with an internal #State if it is not provided in the initializer?
init(text: Binding<Bool>)
This is how to make TextField become first responder in SwiftUI
Note that I know we can pass a constant like:
init(isEditing: Binding<Bool> = .constant(false), text: Binding<Bool>)
But!
This will kill the dynamicity of the variable and it won't work as desire. Imagine re-inventing the isFirstResponder of the UITextField.
It can't be .constant(false). The keyboard will be gone on each view update.
It can't be .constant(true). The view will take the keyboard on each view update.
Maybe! Apple is doing it somehow with TabView.
One solution is to pass an optional binding and use a local state variable if the binding is left nil. This code uses a toggle as an example (simpler to explain) and results in two interactive toggles: one being given a binding and the other using local state.
import SwiftUI
struct ContentView: View {
#State private var isOn: Bool = true
var body: some View {
VStack {
Text("Special toggle:")
SpecialToggle(isOn: $isOn)
.padding()
SpecialToggle()
.padding()
}
}
}
struct SpecialToggle: View {
/// The binding being passed from the parent
var isOn: Binding<Bool>?
/// The fallback state if the binding is left `nil`.
#State private var defaultIsOn: Bool = true
/// A quick wrapper for accessing the current toggle state.
var toggleIsOn: Bool {
return isOn?.wrappedValue ?? defaultIsOn
}
init(isOn: Binding<Bool>? = nil) {
if let isOn = isOn {
self.isOn = isOn
}
}
var body: some View {
Toggle(isOn: isOn ?? $defaultIsOn) {
Text("Dynamic label: \(toggleIsOn.description)")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You may create separate #State and #Binding properties and sync them using onChange or onReceive:
struct TestView: View {
#State private var selectionInternal: Bool
#Binding private var selectionExternal: Bool
init() {
_selectionInternal = .init(initialValue: false)
_selectionExternal = .constant(false)
}
init(selection: Binding<Bool>) {
_selectionInternal = .init(initialValue: selection.wrappedValue)
_selectionExternal = selection
}
var body: some View {
if #available(iOS 14.0, *) {
Toggle("Selection", isOn: $selectionInternal)
.onChange(of: selectionInternal) {
selectionExternal = $0
}
} else {
Toggle("Selection", isOn: $selectionInternal)
.onReceive(Just(selectionInternal)) {
selectionExternal = $0
}
}
}
}
struct ContentView: View {
#State var selection = false
var body: some View {
VStack {
Text("Selection: \(String(selection))")
TestView(selection: $selection)
TestView()
}
}
}

How do I update a text label in SwiftUI?

I have a SwiftUI text label and i want to write something in it after I press a button.
Here is my code:
Button(action: {
registerRequest() // here is where the variable message changes its value
}) {
Text("SignUp")
}
Text(message) // this is the label that I want to change
How do I do this?
With only the code you shared it is hard to say exactly how you should do it but here are a couple good ways:
The first way is to put the string in a #State variable so that it can be mutated and any change to it will cause an update to the view. Here is an example that you can test with Live Previews:
import SwiftUI
struct UpdateTextView: View {
#State var textToUpdate = "Update me!"
var body: some View {
VStack {
Button(action: {
self.textToUpdate = "I've been udpated!"
}) {
Text("SignUp")
}
Text(textToUpdate)
}
}
}
struct UpdateTextView_Previews: PreviewProvider {
static var previews: some View {
UpdateTextView()
}
}
If your string is stored in a class that is external to the view you can use implement the ObservableObject protocol on your class and make the string variable #Published so that any change to it will cause an update to the view. In the view you need to make your class variable an #ObservedObject to finish hooking it all up. Here is an example you can play with in Live Previews:
import SwiftUI
class ExternalModel: ObservableObject {
#Published var textToUpdate: String = "Update me!"
func registerRequest() {
// other functionality
textToUpdate = "I've been updated!"
}
}
struct UpdateTextViewExternal: View {
#ObservedObject var viewModel: ExternalModel
var body: some View {
VStack {
Button(action: {
self.viewModel.registerRequest()
}) {
Text("SignUp")
}
Text(self.viewModel.textToUpdate)
}
}
}
struct UpdateTextViewExternal_Previews: PreviewProvider {
static var previews: some View {
UpdateTextViewExternal(viewModel: ExternalModel())
}
}

SwiftUI #Binding Initialize

Been playing around with SwiftUI and understood the concept of BindableObjects etc so far (at least I hope I do).
I bumped into a stupid problem I can't seem to find an answer for:
How do you initialize a #Binding variable?
I have the following code:
struct LoggedInView : View {
#Binding var dismissView: Bool
var body: some View {
VStack {
Text("Hello World")
}
}
}
In my preview code, I want to pass that parameter of type Binding<Bool>:
#if DEBUG
struct LoggedInView_Previews : PreviewProvider {
static var previews: some View {
LoggedInView(dismissView: **Binding<Bool>**)
}
}
#endif
How would I go an initialize it? tried:
Binding<Bool>.init(false)
Binding<Bool>(false)
Or even:
#Binding var dismissView: Bool = false
But none worked... any ideas?
When you use your LoggedInView in your app you do need to provide some binding, such as an #State from a previous view or an #EnvironmentObject.
For the special case of the PreviewProvider where you just need a fixed value you can use .constant(false)
E.g.
#if DEBUG
struct LoggedInView_Previews : PreviewProvider {
static var previews: some View {
LoggedInView(dismissView: .constant(false))
}
}
#endif
Using Binding.constant(false) is fine but only for static previews. If you actually wanna launch a Live Preview, constant will not behave the same way as the real case as it will never be updated by your actions. I personally use Live Preview a lot, as I can play around with an isolated view.
Here is what I do for previews requiring Binding:
import SwiftUI
struct SomeView: View {
#Binding var code: String
var body: some View {
// some views modifying code binding
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper()
}
struct PreviewWrapper: View {
#State(initialValue: "") var code: String
var body: some View {
SomeView(code: $code)
}
}
}
If you need a simple property that belongs to a single view you
should use #State
If you need to have complex property that may
belong to several view(like 2-3 views) you shall use
#ObjectBinding
Lastly, if you need to have property that needs to use all around views you shall use #EnvironmentObject.
Source for detail information
For your case, if you still would like to initialize your Binding variable you can use:
var binding: Binding = .constant(false)
In preview you have to use .constant(Bool(false)):
#if DEBUG
struct LoggedInView_Previews : PreviewProvider {
static var previews: some View {
LoggedInView(dismissView: .constant(Bool(false))
}
}
#endif
if you have an object like viewModel you can also use .constant()
struct View_Previews: PreviewProvider {
static var previews: some View {
View(vm:.constant(ViewModel(text: "Sample Text")))
}
}
I'm using different configurations of my view within one preview (I'm working on a custom control and want to see a different configuration of it). I've extended the implementation provided by #NeverwinterMoon in order to create multiple independent instances of a view.
struct SomeView: View {
#Binding var value: Int
var body: some View {
// some views modifying code binding
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
VStack {
// The same view but with different configurations
// Configuration #1
PreviewWrapper() { value in
SomeView(value: value)
.background(Color.blue)
}
// Configuration #2
PreviewWrapper(initialValue: 2) { value in
SomeView(value: value)
.padding()
}
}
}
struct PreviewWrapper<Content: View>: View {
#State var value: Int
private let content: (Binding<Int>) -> Content
init(
initialValue: Int = 0,
#ViewBuilder content: #escaping (Binding<Int>) -> Content
) {
self.value = initialValue
self.content = content
}
var body: some View {
content($value)
}
}
}

Resources