I'm providing an extremely simplified example to illustrate my point:
- A single SwiftUI
View
displaying the same properties of aViewModel
- 2
ViewModel
s which areObservableObject
subclasses - The ViewModels have exactly the same properties, just the title/subtitle that are different and the underlying method calls that are different
How to correctly initialize a View
so that it would "own" the ViewModel
?
Normally I go about this as follows:
struct OverrideView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: OverrideViewModelProtocol
init(_ viewModel: @escaping @autoclosure () -> OverrideViewModelProtocol) {
self._viewModel = StateObject(wrappedValue: viewModel())
}
var body: some View {
}
}
However, obviously this doesn't work since I cannot initialize a non-concrete class, the init
is expecting some sort of some OverrideViewModelProtocol
:
protocol OverrideViewModelProtocol: ObservableObject {
var mainTitle: String { get }
var overrideSelectedSegmentIndex: Int { get set }
var overrideCommentHeader: String { get }
var overrideComment: String { get set }
var overrideSubmitButtonEnabled: Bool { get }
var overrideShouldDismiss: Bool { get }
func submitButtonPressed()
}
Obviously, I cannot impose that the OverrideViewModelProtocol
is also an ObservableObject
, therefore I'm getting an issue:
Type 'any OverrideViewModelProtocol' cannot conform to 'ObservableObject'
One way to solve the problem is to create an abstract base class and use it instead of the protocol. But is there a way to use just the protocol and restrict only ObservableObject
subclass to be able to conform to it, so that the View
would know that it's a concrete ObservableObject
subclass on initialization?
Use-case: 2 slightly different views, which differ only in text / button titles, so that I could use 2 different view models instead of if/else statements inside the views.
I'm providing an extremely simplified example to illustrate my point:
- A single SwiftUI
View
displaying the same properties of aViewModel
- 2
ViewModel
s which areObservableObject
subclasses - The ViewModels have exactly the same properties, just the title/subtitle that are different and the underlying method calls that are different
How to correctly initialize a View
so that it would "own" the ViewModel
?
Normally I go about this as follows:
struct OverrideView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: OverrideViewModelProtocol
init(_ viewModel: @escaping @autoclosure () -> OverrideViewModelProtocol) {
self._viewModel = StateObject(wrappedValue: viewModel())
}
var body: some View {
}
}
However, obviously this doesn't work since I cannot initialize a non-concrete class, the init
is expecting some sort of some OverrideViewModelProtocol
:
protocol OverrideViewModelProtocol: ObservableObject {
var mainTitle: String { get }
var overrideSelectedSegmentIndex: Int { get set }
var overrideCommentHeader: String { get }
var overrideComment: String { get set }
var overrideSubmitButtonEnabled: Bool { get }
var overrideShouldDismiss: Bool { get }
func submitButtonPressed()
}
Obviously, I cannot impose that the OverrideViewModelProtocol
is also an ObservableObject
, therefore I'm getting an issue:
Type 'any OverrideViewModelProtocol' cannot conform to 'ObservableObject'
One way to solve the problem is to create an abstract base class and use it instead of the protocol. But is there a way to use just the protocol and restrict only ObservableObject
subclass to be able to conform to it, so that the View
would know that it's a concrete ObservableObject
subclass on initialization?
Use-case: 2 slightly different views, which differ only in text / button titles, so that I could use 2 different view models instead of if/else statements inside the views.
Share Improve this question asked Mar 24 at 12:14 Richard TopchiiRichard Topchii 8,2899 gold badges60 silver badges131 bronze badges 5 |1 Answer
Reset to default 0I've found a very simple and elegant solution:
struct OverrideView<T: OverrideViewModelProtocol>: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: T
init(_ viewModel: @escaping @autoclosure () -> T) {
self._viewModel = StateObject(wrappedValue: viewModel())
}
var body: some View {
}
}
The fact that the OverrideView
was not specialized prevented the compiler from inferring the concrete type. Since the specific kind of ViewModel
is known at a compile time, we just have to specialize that View
over that type.
Works like this:
OverrideView(
ViewModel1()
)
OverrideView(
ViewModel2()
)
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744253073a4565258.html
stateObject
is also a possibility in SwiftUI. – Richard Topchii Commented Mar 26 at 12:46