SwiftUI 结合观察更新

问题描述 投票:0回答:2

我有一个带有支持模型的 SwiftUI 表单。我希望在模型更改时启用“保存”按钮。我有以下代码:

class Model: 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 {
    @StateObject var model = Model()

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

这段代码有两个问题。

第一个问题是模型实例化后,日志将显示“Here”,因此将

didUpdate
设置为 true。第二个问题是,当用户通过文本字段更改模型时,它实际上并没有触发发布者。

这些问题应该如何解决?

(我想过将

didSet{}
添加到模型中的每个属性,但是当属性很多时,这非常难看。我也想过向文本字段添加修饰符,但我真的更喜欢将此代码放在模型,因为网络更新也可能改变模型)。

swiftui combine
2个回答
4
投票

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

首先,您似乎将

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
。当模型与原始模型不同时,保存按钮将被启用,当模型相同时,保存按钮将被禁用。 重要:只有当您使用
struct
作为模型时,这种编写模型的简洁方式才有可能,因为它们具有值语义。也正是因为这个原因,在对任务进行建模时,
structs
比类更受青睐。

现在,如果您坚持使用您的

ViewModel
(例如,因为遵守
Equatable
是不可能或效率低下),您可以做类似的事情。不过,首先请注意这一行

name.publisher

名称(其类型为

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

let name = "Qui-Gon Jinn"

let cancel = name.publisher.print().sink { _ in }

打印

Q
u
i
-
...

您真正想要的是名称的预计值,该名称已经是出版商,即

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

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


2
投票

如果使用 struct 来保存 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

如果您有多个型号,请确认

Identifiable
Equatable
协议。


struct Model: Identifiable, Equatable {
    var id: UUID = UUID()
    
    var name: String
    var color: String
}

© www.soinside.com 2019 - 2024. All rights reserved.