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

SwiftUI遵循MVVM架构时,绑定(Binding)应置于ViewModel中吗?

SwiftUI MVVM架构下正确处理视图绑定的方法

我对SwiftUI中视图绑定的处理和MVVM规范结构存在疑问,以下是一个输入框影响上方文本的基础示例(可正常运行):

struct ContentView: View {
    @State var firstName = "John"
    @State var lastName = "Smith"

    var body: some View {
        VStack {
            Text("first name: \(firstName)")
            Text("last name: \(lastName)")

            ChangeMeView(firstName: $firstName, lastName: $lastName)
        }
    }
}

struct ChangeMeView: View {
    @Binding var firstName: String
    @Binding var lastName: String

    var body: some View {
        VStack {
            TextField("first name", text: $firstName)
            TextField("last name", text: $lastName)
        }
    }
}

但如果遵循MVVM架构,是否需要将firstName、lastName移至对应的ViewModel中?我尝试了两种方案都存在问题:

方案一:ViewModel中存储Binding(运行但不规范)

我尝试将子视图的状态迁移到ViewModel,但直接在ViewModel中存储Binding,感觉不符合MVVM规范:

struct ContentView: View {
    @State var firstName = "John"
    @State var lastName = "Smith"

    var body: some View {
        VStack {
            Text("first name: \(firstName)")
            Text("last name: \(lastName)")

            ChangeMeView(firstName: $firstName, lastName: $lastName)
        }
    }
}

struct ChangeMeView: View {
    @StateObject var viewModel: ViewModel

    init(firstName: Binding<String>, lastName: Binding<String>) {
        _viewModel = StateObject(wrappedValue: ViewModel(firstName: firstName, lastName: lastName))
    }

    var body: some View {
        VStack {
            TextField("first name", text: viewModel.firstName)
            TextField("last name", text: viewModel.lastName)
        }
    }
}

class ViewModel: ObservableObject {
    var firstName: Binding<String>
    var lastName: Binding<String>

    init(firstName: Binding<String>, lastName: Binding<String>) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

方案二:使用@Published但视图不更新

我尝试用@Published实现MVVM,但修改输入框后上方文本不更新:

struct ContentView: View {
     var firstName = "John"
     var lastName = "Smith"

    var body: some View {
        VStack {
            Text("first name: \(firstName)")
            Text("last name: \(lastName)")

            ChangeMeView(viewModel: ViewModel(firstName: firstName, lastName: lastName))
        }
    }
}

struct ChangeMeView: View {
    @ObservedObject var viewModel: ViewModel

    init(viewModel: ViewModel) {
        self.viewModel = viewModel
    }

    var body: some View {
        VStack {
            TextField("first name", text: $viewModel.firstName)
            TextField("last name", text: $viewModel.lastName)
        }
    }
}

class ViewModel: ObservableObject {
    @Published var firstName: String
    @Published var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

正确的MVVM实现方式

要遵循MVVM,核心是让ViewModel作为唯一的状态数据源,所有视图依赖ViewModel的状态,而不是各自维护状态。正确的实现如下:

步骤1:定义ViewModel

ViewModel持有@Published的状态属性,负责管理数据逻辑:

class UserViewModel: ObservableObject {
    @Published var firstName: String
    @Published var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    // 可添加业务逻辑,比如验证、格式化等
    func validateName() -> Bool {
        !firstName.isEmpty && !lastName.isEmpty
    }
}

步骤2:主视图持有ViewModel

在ContentView中用@StateObject持有ViewModel的实例(确保生命周期与视图一致),并直接绑定ViewModel的属性到文本显示:

struct ContentView: View {
    // 用@StateObject创建ViewModel,作为整个视图树的数据源
    @StateObject private var viewModel = UserViewModel(firstName: "John", lastName: "Smith")

    var body: some View {
        VStack(spacing: 20) {
            Text("first name: \(viewModel.firstName)")
            Text("last name: \(viewModel.lastName)")
            
            // 将ViewModel传递给子视图
            ChangeMeView(viewModel: viewModel)
            
            // 也可直接传递绑定,适合子视图不需要整个ViewModel的场景
            // ChangeMeView(firstName: $viewModel.firstName, lastName: $viewModel.lastName)
        }
        .padding()
    }
}

步骤3:子视图使用ViewModel

子视图有两种符合MVVM的实现方式:

方式A:子视图接收整个ViewModel

struct ChangeMeView: View {
    // 用@ObservedObject监听ViewModel的变化
    @ObservedObject var viewModel: UserViewModel

    var body: some View {
        VStack(spacing: 10) {
            TextField("first name", text: $viewModel.firstName)
                .textFieldStyle(.roundedBorder)
            TextField("last name", text: $viewModel.lastName)
                .textFieldStyle(.roundedBorder)
        }
    }
}

方式B:子视图接收单独的Binding

如果子视图只需要特定状态,直接传递Binding更轻量:

struct ChangeMeView: View {
    @Binding var firstName: String
    @Binding var lastName: String

    var body: some View {
        VStack(spacing: 10) {
            TextField("first name", text: $firstName)
                .textFieldStyle(.roundedBorder)
            TextField("last name", text: $lastName)
                .textFieldStyle(.roundedBorder)
        }
    }
}

关键要点总结

  • MVVM核心是ViewModel作为单一数据源,视图仅负责展示和交互,不持有业务状态。
  • 主视图用@StateObject创建ViewModel,确保生命周期与视图绑定;子视图用@ObservedObject接收ViewModel,或直接接收@Binding。
  • 避免在ViewModel中存储Binding,ViewModel应持有具体数据,而非视图的绑定引用。
  • 方案二失败原因是ContentView中的firstName、lastName是普通变量,与ViewModel状态完全独立,正确做法是直接绑定ViewModel的属性到文本显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 03:50:43