swiftui - 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 实例化时,日志将显示“Here”,因此设置didUpdate
为 true。第二个问题是,当用户通过文本字段更改视图模型时,它实际上并没有触发发布者。
这些问题应该如何解决?
(我曾考虑添加didSet{}
到 ViewModel 中的每个属性,但是当有很多属性时,这是非常难看的。我也想过向文本字段添加修饰符,但我更喜欢将这段代码放在 ViewModel 中,因为网络更新也可能改变 ViewModel)。
解决方案
有一种更简单的方法可以做你想做的事,但是这个选项将来可能不是你想要的。但这一切都归结为状态的可变性。
首先,您似乎将 与 混淆Model
了ViewModel
。在您的情况下,模型应该是这样的:
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
. 每当模型与原始模型不同时,将启用保存按钮,如果相同,则禁用保存。重要提示:这种编写模型的简洁方式只有在您使用 astruct
作为模型时才有可能,因为它们具有值语义。出于这个原因,structs
在对任务进行建模时,优先于类。
现在,如果您坚持使用您的ViewModel
(例如,因为Equatable
不可能或效率低下),您可以做类似的事情。然而,首先,请注意这条线
name.publisher
是名称上的发布者(可能是 type Publishers.Sequence<String, Never>
),而不是@Published
值(实际上是 type 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
}
请注意,您需要删除第一个值,因为模型在订阅后立即发布。您还可以将所有这些包装到上述模型中并调用模型的发布者(如果它的属性发生变化,它将在任何时候发布)。
推荐阅读
- java - 如何在 Java 代码中访问可点击的文本视图?
- python - Blender 2.9 + Python 脚本 - 需要帮助将多个动作剪辑/动作片段添加到 NLA 轨道
- python - 将 3.7GB 的大型 json 文件加载到数据帧中,并使用 ijson 转换为 csv 文件
- python - Sorting List of Class Objects by their name(str) attribute with Turkish alphabetic order
- apache-spark - 创建错过的记录 - Hive/PySpark
- c# - 洗牌
- schema - 如何使用转换将字符串转换为 kafka 连接中的时间戳,并使用来自汇合的 jdbc 接收器连接器插入到 postgres 中?
- c++ - 当编译器遇到虚函数时会发生什么?
- java - 我们如何从 ArrayList 添加两个对象