我有一个SummaryView
,Report
作为@State
。
Report
是一种协议,其中包括用户可能想要进行的一些更改:
protocol Report {
var changeGroups: [ChangeGroup] { get set }
}
有几种类型的报告;单个报告被实现为一个结构:
struct RealEstateReport: Report {
static let name = "Real Estate Report"
var changeGroups = [ChangeGroup]()
}
ChangeGroup
是一个结构体,其中包含人类可读的摘要和一些建议的更改:
struct ChangeGroup: Identifiable {
var summary: String
var proposedChanges = [ProposedChange]()
}
ProposedChange
是一个类,表示应用程序向用户提出的一个离散更改,默认情况下为enabled
:
class ProposedChange: ObservableObject, Identifiable {
@Published var enabled = true
let summary: String
(在详细视图中,enabled
绑定到Toggle
,因此用户可以打开和关闭每个提议的更改。(
因此,一个Report
有许多ChangeGroup
s,而CCD_11s
我试图在SummaryView
:上包含一些高级细节
struct SummaryView: View {
@State var report: Report
var body: some View {
Text("Summary")
.foregroundColor(…) // ???
}
我希望foregroundColor
是红色、黄色或绿色:
- 红色如果
enabled
是该Report
中所有ProposedChange
的false
- 绿色如果
enabled
是该Report
中所有ProposedChange
的true
- 黄色如果
enabled
在此Report
中混合了不同的ProposedChange
s
我读过一些关于Combine的文章,我认为我需要为每个ChangeGroup
创建一个新的Combine订阅,并将其映射到每个ProposedChange
的enabled
属性的一个新Combine订阅上,在值发生变化时使其变平,并检查它们是否都相同。
我有点不知道我要用的确切语法。而且structs似乎不会以同样的方式发布更改(我想是因为structs是值类型与引用类型(。
如何根据上述逻辑设置Text视图的foregroundColor
如果ProposedChange
是一个结构而不是一个类,则问题会立即得到解决。除非它的实例有自己的生命周期,否则它们只是值的持有者,因此在语义上应该是一个结构。
问题得到解决的原因是,改变结构的属性会改变结构,因此SwiftUI知道要重新计算视图,而对于类,则需要订阅更改。
假设ProposedChange
是一个结构:
struct ProposedChange {
var enabled = true
var summary: String
}
以下方法应该有效:
struct SummaryView: View {
@State var report: Report
var body: some View {
Text("Summary")
.foregroundColor(summaryColor)
}
var summaryColor: Color {
let count = report.changeGroups.flatMap { $0.proposedChanges }
.map { ($0.enabled ? 1 : 0, 1) }
.reduce((0, 0), { ($0.0 + $1.0, $0.1 + $1.1) })
if count.0 == count.1 { return Color.green }
else if count.0 == 0 { return Color.red }
else { return Color.yellow }
}
}
我最终将所有enabled
标志映射到它们的发布者,使用CombineLatest
运算符将它们全部组合,然后在值更改时重新计算:
class ViewModel: ObservableObject {
enum BoolState {
case allTrue, allFalse, mixed
}
@Published var boolState: BoolState?
private var report: Report
init(report: Report) {
self.report = report
report
.changeGroups // [ChangeGroup]
.map { $0.proposedChanges } // [[ProposedChange]]
.flatMap { $0 } // [ProposedChange]
.map { $0.$enabled } // [AnyPublisher<Bool, Never>]
.combineLatest() // AnyPublisher<[Bool], Never>
.map { Set($0) } // AnyPublisher<Set<Bool>, Never>
.map { boolSet -> BoolState in
switch boolSet {
case [false]:
return .allFalse
case [true]:
return .allTrue
default:
return .mixed
}
} // AnyPublisher<BoolState, Never>
.assign(to: &$boolState)
}
}
注意:
.combineLatest()
不是Combine
的一部分,但它只是我编写的一个扩展,它迭代数组中的每对发布者,并迭代调用它们,比如first.combineLatest(second).combineLatest(third)
等。如果您需要比这更强大的东西,看起来CombineExt项目有一个CombineLatestMany
扩展,它有几个选项。
在这一点上,我的观点只是做了一个@ObservedObject var viewModel: ViewModel
,然后在body
中使用viewModel.boolState
。无论何时enabled
标志因任何原因发生更改,视图都会成功更新!