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 时,日志将显示“此处”,因此设置didUpdate为 true。第二个问题是,当用户通过文本字段更改视图模型时,它实际上并没有触发发布者。

这些问题应该如何解决?

(我曾想过添加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. 当模型与原始模型不同时,将启用保存按钮,当模型相同时,将禁用保存。重要提示:这种编写模型的简洁方式只有在您使用 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
}

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章