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
相关产品推荐
相关产品推荐

