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

SwiftUI:无需ViewController,为各视图配置独立EnvironmentObject(含VM与Interactor)

摆脱ViewController依赖,为SwiftUI视图配置独立的ViewModel+Interactor组合对象

我来帮你梳理下怎么移除对ViewController的依赖,同时让每个SwiftUI视图实例都拥有专属的ViewModel和Interactor组合对象,完全基于SwiftUI的生命周期来管理。

核心思路

我们要抛弃原来通过ViewController传递组合对象的方式,转而利用SwiftUI的@StateObject属性包装器——它会确保每个视图实例都拥有独立的对象实例,并且在视图的整个生命周期内保持存活。同时,我们需要重构Interactor,去掉对UIViewController的依赖,改用SwiftUI原生的导航机制来处理页面跳转。

步骤1:重构Interactor,移除UIViewController依赖

原来的Interactor依赖VC来做页面跳转,现在我们可以用回调闭包来通知视图执行导航操作,把具体的跳转逻辑交还给SwiftUI视图处理:

import SwiftUI
import Combine

class ViewModel1: ObservableObject {
    @Published var text: String
    init(text: String) {
        self.text = text
    }
}

class Interactor1: ObservableObject {
    let viewModel: ViewModel1
    // 定义导航回调,由视图实现具体跳转逻辑
    var onButtonClicked: (() -> Void)?
    
    init(viewModel: ViewModel1) {
        self.viewModel = viewModel
    }
    
    func buttonClicked() {
        // 触发回调,让视图处理导航
        onButtonClicked?()
    }
}

步骤2:重新设计CombinedObject

不再需要传入ViewController参数,直接在初始化时创建对应的ViewModel和Interactor,同时监听两者的变化并转发给组合对象的objectWillChange,确保视图能感知到内部数据的更新:

class CombinedObject: ObservableObject {
    @Published var interactor: Interactor1
    @Published var viewModel: ViewModel1
    // 用Set来管理订阅,避免内存泄漏
    private var cancellables = Set<AnyCancellable>()
    
    init(initialText: String) {
        self.viewModel = ViewModel1(text: initialText)
        self.interactor = Interactor1(viewModel: viewModel)
        
        // 转发Interactor的变化
        interactor.objectWillChange
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
        
        // 转发ViewModel的变化
        viewModel.objectWillChange
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
}

步骤3:在SwiftUI视图中使用@StateObject创建独立组合对象

每个视图通过@StateObject初始化自己的CombinedObject,确保实例独立。同时在视图中处理Interactor的导航回调,用SwiftUI原生组件(比如sheet、NavigationStack)实现页面跳转:

struct View1: View {
    // 每个View1实例都会拥有专属的CombinedObject
    @StateObject private var combinedObject = CombinedObject(initialText: "text1")
    @State private var showSecondView = false
    
    var body: some View {
        VStack(spacing: 20) {
            Button(action: {
                combinedObject.interactor.buttonClicked()
            }) {
                Text(combinedObject.viewModel.text)
                    .font(.title)
            }
        }
        .onAppear {
            // 给Interactor设置导航回调
            combinedObject.interactor.onButtonClicked = {
                showSecondView = true
            }
        }
        // 用SwiftUI的sheet实现页面跳转
        .sheet(isPresented: $showSecondView) {
            View2()
        }
    }
}

// 示例第二个视图,同样拥有独立的组合对象
struct View2: View {
    @StateObject private var combinedObject = CombinedObject(initialText: "text2")
    
    var body: some View {
        VStack {
            Text(combinedObject.viewModel.text)
                .font(.title)
            Button("返回") {
                // 利用环境中的dismiss动作关闭sheet
                dismiss()
            }
        }
        .environment(\.dismiss, DismissAction())
    }
}

关键优势

  • 完全摆脱ViewController依赖:整个架构基于SwiftUI的生命周期,不需要UIViewController作为中介。
  • 视图实例独立:每个视图的CombinedObject、ViewModel、Interactor都是独立的,不会出现状态共享混乱的问题。
  • 符合SwiftUI设计理念:导航逻辑由SwiftUI原生组件处理,数据更新通过ObservableObject和属性包装器自动同步到视图。

如果你需要在多个视图间共享某些全局状态,可以在父视图创建对应的对象并通过environmentObject(_:)传递,但对于每个视图的专属状态,@StateObject是最优选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 19:42:34