SwiftUI/Conine订阅多个嵌套集合中的更新



我有一个SummaryViewReport作为@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有许多ChangeGroups,而CCD_11s

我试图在SummaryView:上包含一些高级细节

struct SummaryView: View {
@State var report: Report

var body: some View {
Text("Summary")
.foregroundColor(…) // ???
}

我希望foregroundColor是红色、黄色或绿色:

  • 红色如果enabled是该Report中所有ProposedChangefalse
  • 绿色如果enabled是该Report中所有ProposedChangetrue
  • 黄色如果enabled在此Report中混合了不同的ProposedChanges

我读过一些关于Combine的文章,我认为我需要为每个ChangeGroup创建一个新的Combine订阅,并将其映射到每个ProposedChangeenabled属性的一个新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标志因任何原因发生更改,视图都会成功更新!

相关内容

  • 没有找到相关文章

最新更新