最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - SwiftUI MVVM: Single View, 2 different ViewModels that conform to ObservableObject - Stack Overflow

programmeradmin3浏览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.

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.

Share Improve this question asked Mar 24 at 12:14 Richard TopchiiRichard Topchii 8,2899 gold badges60 silver badges131 bronze badges 5
  • You're supposed to use View structs and State/Binding not classes – malhal Commented Mar 25 at 18:17
  • This is a common pattern and using a stateObject is also a possibility in SwiftUI. – Richard Topchii Commented Mar 26 at 12:46
  • Common mistake morelike. StateObject is for asynchronous delegates or combine pipeline. – malhal Commented Mar 26 at 18:59
  • Could you please clarify what these assumptions/statements are based on? – Richard Topchii Commented Mar 26 at 19:57
  • 1 This should help you learn it developer.apple/videos/play/wwdc2020-10040 – malhal Commented Mar 27 at 5:48
Add a comment  | 

1 Answer 1

Reset to default 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()
)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论