首页 > 解决方案 > 如何将`@Binding`重写为ViewModel

问题描述

我只想在下面做一个ViewModelCustomTextField

import SwiftUI

struct ContentView: View {
  @State private var text = ""

  var body: some View {
    VStack {
      CustomTextField(text: $text)
      Text(text)
    }
  }
}

struct CustomTextField: View {
  @Binding var text: String

  var body: some View {
    TextField("title", text: $text)
  }
}

我尝试按如下方式重写它,但我不知道如何正确地重写它。

import SwiftUI
import Combine

struct ContentView: View {
  @State private var text = ""

  var body: some View {
    VStack {
      CustomTextField(text: $text)
      Text(text)
    }
  }
}

struct CustomTextField: View {
  @StateObject private var viewModel = CustomTextFieldViewModel()

//  @Binding var text: String <-- How to replace ViewModel

  var body: some View {
    TextField("title", text: $text)
  }
}

final class CustomTextFieldViewModel: ObservableObject {
  private var cancellables: Set<AnyCancellable> = []

  @Published var text = ""
}

如果我不使用@Binding,我将无法以父母的观点进行通知。这CustomTextField是模块化的,我希望它能够被任何父母使用,但我不知道如何编写它来模仿@Binding使用 ViewModel。(换句话说,我想以某种方式链接ContentView's textViewModel's text。)

我这样做的方式有什么根本错误吗?

或者

Property Wrappers是否可以通过使用另一个不是的来解决问题@Published

或者

有没有办法初始化 ViewModel 来解决这个问题?

标签: iosswiftui

解决方案


如果我理解正确,您希望@State在父视图 ( ContentView) 中使用 a ViewModel,在子视图 ( CustomTextField) 中使用 a。所以你必须@Binding在你的 ViewModel 中声明:

final class CustomTextFieldViewModel: ObservableObject {
    @Binding var text: String
    init(text: Binding<String>) {
        _text = text
    }
}

struct CustomTextField: View {
    @ObservedObject private var viewModel: CustomTextFieldViewModel
    init(text: Binding<String>) {
        viewModel = CustomTextFieldViewModel(text: text)
    }

  var body: some View {
    TextField("title", text: $viewModel.text)
  }
}

struct ContentView: View {
  @State private var text = ""

  var body: some View {
    VStack {
      CustomTextField(text: $text)
      Text(text)
    }
  }
}

但是,您不应该使用此解决方案。@Binding并且@State只在Views 中使用。

您可以使用单个 ViewModel,在父视图中实例化,并由两个视图共享(如在此答案中-编辑:或在@workingdog 的答案中)。

或者,如果您想使用两种不同的 ViewModel(一个用于管理ContentView,另一个用于CustomTextField),您可以使用 Combine :

struct ContentView: View {
  @StateObject private var vm = ContentViewViewModel()

  var body: some View {
    VStack {
        CustomTextField(viewModel: vm.customTextFieldVM)
        TextField("", text: $vm.text)
        Text(vm.text)
    }
  }
}

struct CustomTextField: View {
    @ObservedObject var viewModel: CustomTextFieldViewModel

  var body: some View {
      TextField("title", text: $viewModel.text)
  }
}


final class ContentViewViewModel: ObservableObject {
    @Published var text: String {
        didSet {
            if text != self.customTextFieldVM.text {
                self.customTextFieldVM.text = text
            }
        }
    }
    var customTextFieldVM: CustomTextFieldViewModel
    var cancellables: Set<AnyCancellable> = []
    init() {
        text = ""
        customTextFieldVM = CustomTextFieldViewModel()
        customTextFieldVM.$text
            .sink {
                if $0 != self.text {
                    self.text = $0
                }
            }
            .store(in: &cancellables)
    }
}
final class CustomTextFieldViewModel: ObservableObject {
    @Published var text: String
    init() {
        text = ""
    }
}

推荐阅读