SwiftUI / Combine:侦听数组项值更改
SwiftUI / Combine : Listening array items value change
我想显示多个文本字段,代表比赛每个部分的分数。
示例:对于一场排球比赛,我们有 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)
}
}
}
父组件需要显示比赛各部分的总分。我想尝试使用 Combine binding/stream 来获得总分。
我尝试了多种解决方案,但最终得到了这段无效代码(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 = [=12=]
}
}
}
init(model: Model) {
self.model = model
totalScoreModel = TotalScoreModel(
publisher: Publishers.MergeMany(
model.scores.map {
Publishers.CombineLatest([=12=].$firstScore, [=12=].$secondScore)
.map { ([=12=].0, [=12=].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 { "[\([=12=].0)] - [\([=12=].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 { ([=10=].firstScore, [=10=].secondScore) }
.reduce((0,0)) { .0 > .1 ? ( [=10=].0 + 1, [=10=].1 ) : ([=10=].0, [=10=].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 { [=10=].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)
}
我想显示多个文本字段,代表比赛每个部分的分数。
示例:对于一场排球比赛,我们有 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)
}
}
}
父组件需要显示比赛各部分的总分。我想尝试使用 Combine binding/stream 来获得总分。
我尝试了多种解决方案,但最终得到了这段无效代码(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 = [=12=]
}
}
}
init(model: Model) {
self.model = model
totalScoreModel = TotalScoreModel(
publisher: Publishers.MergeMany(
model.scores.map {
Publishers.CombineLatest([=12=].$firstScore, [=12=].$secondScore)
.map { ([=12=].0, [=12=].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 { "[\([=12=].0)] - [\([=12=].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 { ([=10=].firstScore, [=10=].secondScore) }
.reduce((0,0)) { .0 > .1 ? ( [=10=].0 + 1, [=10=].1 ) : ([=10=].0, [=10=].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 { [=10=].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)
}