我想显示多个文本字段,以表示比赛各部分的得分。
示例:对于排球比赛,我们有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来获得总分。
我尝试了多种解决方案,但最终得到了这个无效的代码(缩减似乎不是采用数组的所有元素,而是内部存储了先前的结果):
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中。
不适用:TextFieldsComponent也需要独立使用。
答案 0 :(得分:2)
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)
}