swift - SwiftUI / Combine:监听数组项值变化
问题描述
我想显示多个文本字段,代表比赛每个部分的分数。
示例:对于排球比赛,我们有 25/20、25/22、25/23。全球得分为 3/0。
全局组件架构:
>> ParentComponent
>> MainComponent
>> X TextFieldsComponent (2 text fields, home/visitor score)
最低的组件 TextFieldsComponent 包含基本绑定:
struct TextFieldsComponent: View {
@ObservedObject var model: Model
class Model: ObservableObject, Identifiable, CustomStringConvertible {
let id: String
@Published var firstScore: String
@Published var secondScore: String
var description: String {
"\(firstScore) \(secondScore)"
}
init(id: String, firstScore: String = .empty, secondScore: String = .empty) {
self.id = id
self.firstScore = firstScore
self.secondScore = secondScore
}
}
var body: some View {
HStack {
TextField("Dom.", text: $model.firstScore)
.keyboardType(.numberPad)
TextField("Ext.", text: $model.secondScore)
.keyboardType(.numberPad)
}
}
}
父组件需要显示匹配所有部分的总分。我想尝试一个组合绑定/流来获得总分。
我尝试了多种解决方案,最终得到了这个不起作用的代码(reduce 似乎不是采用数组的所有元素,而是在内部存储了以前的结果):
struct MainComponent: View {
@ObservedObject var model: Model
@ObservedObject private var totalScoreModel: TotalScoreModel
class Model: ObservableObject {
@Published var scores: [TextFieldsComponent.Model]
init(scores: [TextFieldsComponent.Model] = [TextFieldsComponent.Model(id: "main")]) {
self.scores = scores
}
}
private final class TotalScoreModel: ObservableObject {
@Published var totalScore: String = ""
private var cancellable: AnyCancellable?
init(publisher: AnyPublisher<String, Never>) {
cancellable = publisher.print().sink {
self.totalScore = $0
}
}
}
init(model: Model) {
self.model = model
totalScoreModel = TotalScoreModel(
publisher: Publishers.MergeMany(
model.scores.map {
Publishers.CombineLatest($0.$firstScore, $0.$secondScore)
.map { ($0.0, $0.1) }
.eraseToAnyPublisher()
}
)
.reduce((0, 0), { previous, next in
guard let first = Int(next.0), let second = Int(next.1) else { return previous }
return (
previous.0 + (first == second ? 0 : (first > second ? 1 : 0)),
previous.1 + (first == second ? 0 : (first > second ? 0 : 1))
)
})
.map { "[\($0.0)] - [\($0.1)]" }
.eraseToAnyPublisher()
)
}
var body: some View {
VStack {
Text(totalScoreModel.totalScore)
ForEach(model.scores) { score in
TextFieldsComponent(model: score)
}
}
}
}
我正在寻找一种解决方案来获取每个绑定更改的事件,并将其合并到一个流中,以在 MainComponent 中显示它。
N/B:TextFieldsComponent 也需要独立使用。
解决方案
MergeMany
是正确的方法,因为你自己开始了,虽然我认为你把事情复杂化了。
如果您想在视图中显示总分(假设总分由Model
而不是“拥有” TotalScoreModel
,这是有道理的,因为它拥有基础分数),那么您需要表明该模型将在何时更改任何基础分数都会改变。
然后您可以将总分作为计算属性提供,SwiftUI 将在重新创建视图时读取更新后的值。
class Model: ObservableObject {
@Published var scores: [TextFieldsComponent.Model]
var totalScore: (Int, Int) {
scores.map { ($0.firstScore, $0.secondScore) }
.reduce((0,0)) { $1.0 > $1.1 ? ( $0.0 + 1, $0.1 ) : ($0.0, $0.1 + 1) }
}
private var cancellables = Set<AnyCancellable>()
init(scores: [TextFieldsComponent.Model] = [.init(id: "main")]) {
self.scores = scores
// get the ObservableObjectPublisher publishers
let observables = scores.map { $0.objectWillChange }
// notify that this object will change when any of the scores change
Publishers.MergeMany(observables)
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}
然后,在视图中,您可以Model.totalScore
像往常一样使用:
@ObservedObject var model: Model
var body: some View {
Text(model.totalScore)
}
推荐阅读
- eclipse - 如何在 Eclipse 中更改 Tomcat 的默认工作目录(不是工作区)?
- r - 大于当前值且跨不同组的值的总和
- javascript - 如何使用jquery设置文本超过五行时打开的点和按钮符号?
- node.js - Node.js 17.0.1 Gatsby 错误-“数字信封例程::不支持 ... ERR_OSSL_EVP_UNSUPPORTED”
- javascript - 如何将应用程序元数据传递给类方法?
- azure - 如何查找 Azure AKS 传出 IP
- javascript - tableau server 受信任的身份验证
- amazon-web-services - 将 SageMaker Notebook 复制到 SageMaker Studio
- windows-services - Wix Bootstrapper 在卸载 MSI 期间失败 - 无法停止服务
- android - 使用“registerForActivityResult”获取错误的文件路径