0

I'm providing an extremely simplified example to illustrate my point:

  • A single SwiftUI View displaying the same properties of a ViewModel
  • 2 ViewModels which are ObservableObject 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.

5
  • You're supposed to use View structs and State/Binding not classes Commented Mar 25 at 18:17
  • This is a common pattern and using a stateObject is also a possibility in SwiftUI. Commented Mar 26 at 12:46
  • Common mistake morelike. StateObject is for asynchronous delegates or combine pipeline. Commented Mar 26 at 18:59
  • Could you please clarify what these assumptions/statements are based on? Commented Mar 26 at 19:57
  • 1
    This should help you learn it developer.apple.com/videos/play/wwdc2020-10040 Commented Mar 27 at 5:48

1 Answer 1

0

I'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()
)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.