SwiftUI组合观察更新



我有一个支持ViewModel的SwiftUI窗体。我希望在ViewModel更改时启用"保存"按钮。我有以下代码:

class ViewModel: ObservableObject {
@Published var didUpdate = false
@Published var name = "Qui-Gon Jinn"
@Published var color = "green"
private var cancellables: [AnyCancellable] = []
init() {
self.name.publisher.combineLatest(self.color.publisher)
.sink { _ in
NSLog("Here")
self.didUpdate = true
}
.store(in: &self.cancellables)
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
Form {
Toggle(isOn: self.$viewModel.didUpdate) {
Text("Did update:")
}
TextField("Enter name", text: self.$viewModel.name)
TextField("Lightsaber color", text: self.$viewModel.color)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationBarItems(
trailing:
Button("Save") { NSLog("Saving!") }
.disabled(!self.viewModel.didUpdate)
)
}
}
}

此代码有两个问题。

第一个问题是,在实例化ViewModel时,日志将显示";在这里";,从而将CCD_ 1设置为真。第二个问题是,当用户通过文本字段更改视图模型时,实际上并没有解雇发布者。

应该如何解决这些问题?

(我曾想过将didSet{}添加到ViewModel中的每个属性中,但当有很多属性时,这非常糟糕。我也曾想过在文本字段中添加修饰符,但我真的更喜欢将这些代码放在ViewModel中,因为网络更新也可能更改ViewModel(。

有一种更简单的方法可以做你想要的事情,但这个选项在未来可能不是你想要的。但归根结底,这一切都是状态的可变性

首先,您似乎混淆了ModelViewModel。在您的情况下,模型应该是这样的:

struct Model: Equatable {
var name = "Qui-Gon Jinn"
var color = "green"
}

请注意,您的型号是Equatable。在swift中,将为您合成的默认实现只需检查所有元素是否彼此相等,即默认实现看起来像这样:

static func ==(lhs: Model, rhs: Model) -> Bool {
lhs.name == rhs.name && lhs.color == rhs.color
}

我们可以使用这种行为来获得所需的结果:

struct ContentView: View {

var original: Model
@State var updated: Model

init(original: Model) {
self.original = original
self.updated = original
}

var body: some View {
NavigationView {
Form {
TextField("Enter name", text: $updated.name)
TextField("Lightsaber color", text: $updated.color)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationBarItems(
trailing:
Button("Save") { NSLog("Saving!") }
.disabled(original == updated)
)
}
}
}

现在,您可以简单地将一个旧(或新(模型传递给ContentView。当模型与原始模型不同时,将启用"保存"按钮,当模型相同时,将禁用"保存">重要:只有当您使用struct作为模型时,这种简洁的编写模型的方式才是可能的,因为它们具有值语义。也正是由于这个原因,在对任务建模时,structs比类更受欢迎。

现在,如果您坚持使用ViewModel(例如,因为与didUpdate0的一致性不可能或效率低下(,则可以执行类似的操作。然而,首先要注意的是,这条线

name.publisher

名称(其类型为Publishers.Sequence<String, Never>(上的发布者,而不是@Published值(其类型实际上为Published<String>.Publisher(前者发布字符串的每个字符,即这个

let name = "Qui-Gon Jinn"
let cancel = name.publisher.print().sink { _ in }

打印

Q
u
i
-
...

您实际想要的是名称的投影值,它已经是一个发布者,即

$name.dropFirst().sink { _ in
NSLog("Here")
self.didUpdate = true
}

请注意,您需要删除第一个值,因为模型在订阅后立即发布。您还可以将所有这些打包到前面提到的模型中,并调用模型的发布者(如果模型的属性发生变化,它将在任何时候发布(。

如果使用结构来保存From字段的属性,会更容易。

struct Model {
var name: String
var color: String
}

然后,在self.$model.sink { value in}中,比较新值是否与旧值相同或是否发生了变化。

class ViewModel: ObservableObject {
@Published var didUpdate = false
@Published var model: Model
private var cancellables: [AnyCancellable] = []

init() {
self.model = Model(name: "Qui-Gon Jinn", color: "green")
self.$model.sink { value in

guard !(value.name.trimmingCharacters(in: .whitespaces).isEmpty || value.color.trimmingCharacters(in: .whitespaces).isEmpty) else {
self.didUpdate = false
return
}
if value.name != self.model.name {
NSLog("name did chanage")
self.didUpdate = true
}

if value.color != self.model.color {
NSLog("Color did change")
self.didUpdate = true
}

}
.store(in: &self.cancellables)
}

deinit {
self.cancellables.removeAll()
}
}

所有代码


struct Model {
var name: String
var color: String
}
class ViewModel: ObservableObject {
@Published var didUpdate = false
@Published var model: Model
private var cancellables: [AnyCancellable] = []

init() {
self.model = Model(name: "Qui-Gon Jinn", color: "green")
self.$model.sink { value in

guard !(value.name.trimmingCharacters(in: .whitespaces).isEmpty || value.color.trimmingCharacters(in: .whitespaces).isEmpty) else {
self.didUpdate = false
return
}

if value.name != self.model.name {
NSLog("Here")
self.didUpdate = true
}

if value.color != self.model.color {
NSLog("Here")
self.didUpdate = true
}

}
.store(in: &self.cancellables)
}

deinit {
self.cancellables.removeAll()
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()

var body: some View {
NavigationView {
Form {
Toggle(isOn: self.$viewModel.didUpdate) {
Text("Did update:")
}
TextField("Enter name", text: self.$viewModel.model.name)
TextField("Lightsaber color", text: self.$viewModel.model.color)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationBarItems(
trailing:
Button("Save") { NSLog("Saving!") }
.disabled(!self.viewModel.didUpdate)
)
}
}
}

🎁

1

.navigationBarItems已弃用。请改用.toolbar

.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") { NSLog("Saving!") }
.disabled(!self.viewModel.didUpdate)
}
}
  1. https://developer.apple.com/documentation/swiftui/view/navigationbaritems(前:后:(

  2. https://developer.apple.com/documentation/swiftui/view/toolbar(内容:(-5w0tj

2

如果您有多个型号,请按照IdentifiableEquatable协议进行确认。


struct Model: Identifiable, Equatable {
var id: UUID = UUID()

var name: String
var color: String
}

最新更新