SwiftUI MVVM:单视图适配多ObservableObject视图模型的初始化方案
现有场景:一个SwiftUI View需要展示ViewModel的相同属性,存在两个继承自ObservableObject的ViewModel,二者属性结构完全一致,仅文本标题及底层方法实现不同。
尝试通过协议定义ViewModel:
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() }
并初始化View以使其拥有ViewModel:
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 { } }
但出现错误:Type 'any OverrideViewModelProtocol' cannot conform to 'ObservableObject'。已知可通过抽象基类解决,但希望仅使用协议,限制仅ObservableObject子类可遵循该协议,询问如何正确初始化View以使其拥有符合要求的ViewModel?
直接使用any OverrideViewModelProtocol作为@StateObject的类型会报错,因为@StateObject要求存储的是具体的、遵循ObservableObject的类型,而存在类型any Protocol无法满足这个要求。我们可以通过让OverrideView变成泛型View,约束泛型类型遵循OverrideViewModelProtocol来解决:
修改后的View代码:
struct OverrideView<ViewModel: OverrideViewModelProtocol>: View { @Environment(\.dismiss) private var dismiss @StateObject private var viewModel: ViewModel init(_ viewModel: @escaping @autoclosure () -> ViewModel) { self._viewModel = StateObject(wrappedValue: viewModel()) } var body: some View { // 示例:使用协议定义的属性与方法 VStack(spacing: 16) { Text(viewModel.mainTitle) .font(.title) Picker("", selection: $viewModel.overrideSelectedSegmentIndex) { Text("选项1").tag(0) Text("选项2").tag(1) } .pickerStyle(.segmented) TextField(viewModel.overrideCommentHeader, text: $viewModel.overrideComment) .textFieldStyle(.roundedBorder) Button("提交", action: viewModel.submitButtonPressed) .disabled(!viewModel.overrideSubmitButtonEnabled) .buttonStyle(.borderedProminent) } .padding() .onChange(of: viewModel.overrideShouldDismiss) { shouldDismiss in if shouldDismiss { dismiss() } } } }
代码说明
- 泛型约束:通过
ViewModel: OverrideViewModelProtocol泛型约束,确保传入的ViewModel必须是遵循协议的具体类型——由于协议已经继承了ObservableObject,泛型类型天然满足@StateObject的类型要求。 - @StateObject初始化:泛型参数
ViewModel会被替换为具体的类类型(比如自定义的FirstViewModel),因此可以直接赋值给@StateObject,不会出现类型不兼容的错误。 - 具体ViewModel实现示例:
class FirstViewModel: OverrideViewModelProtocol { @Published var mainTitle: String = "第一个场景标题" @Published var overrideSelectedSegmentIndex: Int = 0 var overrideCommentHeader: String = "请输入评论(至少1字)" @Published var overrideComment: String = "" var overrideSubmitButtonEnabled: Bool { !overrideComment.trimmingCharacters(in: .whitespaces).isEmpty } @Published var overrideShouldDismiss: Bool = false func submitButtonPressed() { // 自定义提交逻辑 print("第一个场景提交:\(overrideComment)") overrideShouldDismiss = true } } class SecondViewModel: OverrideViewModelProtocol { @Published var mainTitle: String = "第二个场景标题" @Published var overrideSelectedSegmentIndex: Int = 0 var overrideCommentHeader: String = "留下你的想法(至少5字)" @Published var overrideComment: String = "" var overrideSubmitButtonEnabled: Bool { overrideComment.trimmingCharacters(in: .whitespaces).count >= 5 } @Published var overrideShouldDismiss: Bool = false func submitButtonPressed() { // 自定义提交逻辑 print("第二个场景提交:\(overrideComment)") overrideShouldDismiss = true } }
- View使用方式:初始化时直接传入具体ViewModel实例,Swift会自动推导泛型类型:
// 在父View中使用 OverrideView(FirstViewModel()) OverrideView(SecondViewModel())
核心原理
SwiftUI的@StateObject需要管理具体ObservableObject实例的生命周期,泛型参数ViewModel会被替换为实际的类类型,让@StateObject能正确识别并管理实例生命周期。同时通过协议约束,我们依然保持了View与ViewModel之间的抽象依赖,符合面向协议编程的设计思路。
内容的提问来源于stack exchange,提问作者Richard Topchii

