SwiftUI Combine 观察更新

4

我有一个使用SwiftUI Form的ViewModel。当ViewModel改变时,我希望启用Save按钮。下面是代码:

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。第二个问题是当用户通过文本字段更改viewmodel时,它实际上不会触发发布器。

应该如何解决这些问题?

(我考虑了为ViewModel中的每个属性添加didSet{},但当有很多属性时,这样做非常丑陋。我也考虑过向文本字段添加修饰符,但我真的更喜欢将此代码放在ViewModel中,因为网络更新也可能更改ViewModel)。

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)。 前者发布字符串的每个字符,例如:

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

如果使用一个结构体来保存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(leading:trailing:)

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

2

如果您有多个模型,请确认符合IdentifiableEquatable协议。


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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接