You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
            }
        }
    }
}

代码说明

  1. 泛型约束:通过ViewModel: OverrideViewModelProtocol泛型约束,确保传入的ViewModel必须是遵循协议的具体类型——由于协议已经继承了ObservableObject,泛型类型天然满足@StateObject的类型要求。
  2. @StateObject初始化:泛型参数ViewModel会被替换为具体的类类型(比如自定义的FirstViewModel),因此可以直接赋值给@StateObject,不会出现类型不兼容的错误。
  3. 具体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
    }
}
  1. View使用方式:初始化时直接传入具体ViewModel实例,Swift会自动推导泛型类型:
// 在父View中使用
OverrideView(FirstViewModel())
OverrideView(SecondViewModel())

核心原理

SwiftUI的@StateObject需要管理具体ObservableObject实例的生命周期,泛型参数ViewModel会被替换为实际的类类型,让@StateObject能正确识别并管理实例生命周期。同时通过协议约束,我们依然保持了View与ViewModel之间的抽象依赖,符合面向协议编程的设计思路。

内容的提问来源于stack exchange,提问作者Richard Topchii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:00:10