init(viewModel: SignInViewModel) {
self.viewModel = viewModel
$$self.viewModel = State<SignInViewModel>(initialValue: viewModel) // It should be this. @State use delegateValue, so it's $$self.viewModel. Unfortunately it dose not work.
}
Although that will compile, @State variables in SwiftUI should not be initialized from data you pass down through the initializer; since the model is maintained outside of the view, there is no guarantee that the value will really be used. The correct thing to do is to set your initial state values inline:
@State private var viewModel = SignInViewModel(...)
A requirement to set it inline prevents dependency injection by constructor and impacts testability. I want to be able to inject a mock state in my view tests.
An @ObjectBinding might work better then; you can then bind to any class conforming to the BindableObject protocol, and provide a mock implementation for your tests. State is primarily intended for small-scale UI state.
For dependency injection, it might be better to do it at the type level, something like this:
@State is allowing me to bind the TextField changes and Button actions to my view model. Here's what my view model looks like:
final class SignInViewModel {
var email = ""
var password = ""
func signInTapped() { print("sign in tapped") }
}
It's working nicely - typing in the text fields would update the stored properties in the view model, and the button action maps perfectly to the signInTapped function.
I'm running into the same issue. Anyone have any thoughts on how to fix this? In particular I'm trying to do a binding at the tail end of a publisher: // .assign(to: \StoryListView.stories, on: listView).
I get the error:
hi @Joe_Groff,
Is there a chance that you can provide a simple functional code snippet that demonstrates initialising a custom structure with a State set from outside? This is the first stumbling block as there is not a lot of Good basic documentation on how to use and understand SwiftUI and Bindings with State or Binding, etc.
Quick update on this thread. I came up with a satisfying pattern that allows views to depend on an abstract definition of the view model that allows you to inject from the outside:
protocol SignInModel: ObservableObject {
var email: String { get set }
var password: String { get set }
func signIn() -> Future<Void, Error>
}
struct SignInView<Model: SignInModel>: View {
@ObservedObject private(set) var model: Model
}
Everything defined in the protocol has the ability to signal the dependents to re-render their view, assuming that's what the view subscribes to.
But the compiler does generate a default initialiser for @State variables (Xcode 11.3.1). I suspect this is why people think they should be able to write them themselves (at least that's what it was like for me). This seems like potentially dangerous behaviour - should i file a bug?
But what if you have two cases, one where you want to pass data from a preceding view and another where you want to initialize with default data values (ie using a view as both a creation and edit page)? Bindings do not allow that.
only work one time at view insert, but if the view is simply re-initialized by the parent, .onAppear() is not called anymore. By giving the view a new .id(...), the view is brand new.
Hmm, I see to it that the view (forces the user to) close the form before linking it to a new one. You'd allow accidental switch otherwise. Different design I guess, but yes, if you want to update on re-link, you can use .id to invalidate the old view.
Wow what a trip. For now I've gone ahead with @Lantua's suggestion of using .onAppear as it seems more kosher. Hoping this behavior is resolved or at least throws an error by WWDC.
Thanks for the link! I've been digging the forums for so long to research this issue so this helped a lot.