SwiftUI/Combine在多个嵌套集合中订阅更新
我有一个SwiftUI/Combine在多个嵌套集合中订阅更新,swift,swiftui,combine,Swift,Swiftui,Combine,我有一个摘要视图,其中报告为@State 报告是一种协议,其中包括用户可能希望进行的一些更改: protocol Report { var changeGroups: [ChangeGroup] { get set } } 有几种报告;单个报告作为结构实现: struct RealEstateReport: Report { static let name = "Real Estate Report" var changeGroups =
摘要视图
,其中报告
为@State
报告
是一种协议,其中包括用户可能希望进行的一些更改:
protocol Report {
var changeGroups: [ChangeGroup] { get set }
}
有几种报告;单个报告作为结构实现:
struct RealEstateReport: Report {
static let name = "Real Estate Report"
var changeGroups = [ChangeGroup]()
}
struct ProposedChange {
var enabled = true
var summary: String
}
ChangeGroup
是一个结构,其中包含(除其他内容外)一个可读的摘要和一些建议的更改:
struct ChangeGroup: Identifiable {
var summary: String
var proposedChanges = [ProposedChange]()
}
ProposedChange
是一个类,表示应用程序向用户建议的一个离散更改,默认情况下启用该更改:
class ProposedChange: ObservableObject, Identifiable {
@Published var enabled = true
let summary: String
(在详细视图中,启用
绑定到一个切换
,因此用户可以打开和关闭每个建议的更改。)
因此,一份报告
有许多变更组
s,这些变更组本身有许多提议的变更
s
我试图在摘要视图
中包含一些高级细节:
struct SummaryView: View {
@State var report: Report
var body: some View {
Text("Summary")
.foregroundColor(…) // ???
}
我希望foregroundColor
为红色、黄色或绿色:
- 红色如果本
报告中所有
s的提议的变更
为已启用
假
- 绿色如果本
报告中所有
s的提议的变更
为已启用
真
- 黄色如果此
报告中的不同
s混合了建议变更
启用
变更组
创建一个新的联合收割机订阅,并将其映射到每个建议变更
的已启用
属性的一个新联合收割机订阅,在一个值发生更改时将其展平,并检查它们是否都相同
我对我将使用的确切语法有点迷茫。而且结构似乎不会以相同的方式发布更改(我猜,因为结构是值类型和引用类型)
如何根据上述逻辑设置文本视图的foregroundColor?如果
ProposedChange
是一个结构而不是一个类,您的问题将立即得到解决。除非它的实例有自己的生命周期,否则它们只是值的持有者,所以在语义上应该是一个结构
问题得以解决的原因是,改变结构的属性会改变结构,因此SwiftUI知道如何重新计算视图,而对于类,则需要订阅更改
假设ProposedChange
是一个结构:
struct RealEstateReport: Report {
static let name = "Real Estate Report"
var changeGroups = [ChangeGroup]()
}
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 }
}
}
最后,我将所有启用的
标志映射到它们的发布者,使用combinelateest
操作符将它们组合起来,然后在值更改时重新计算:
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)
}
}
类视图模型:ObserveObject{
枚举布尔状态{
大小写全部正确,全部错误,混合
}
@发布的var boolState:boolState?
私人var报告:报告
初始(报告:报告){
self.report=报告
报告
.changeGroups/[ChangeGroup]
.map{$0.proposedChanges}/[[ProposedChange]]
.flatMap{$0}/[ProposedChange]
.map{$0.$enabled}/[AnyPublisher]
.combineLatest()//AnyPublisher
.map{Set($0)}//AnyPublisher
.map{boolSet->BoolState在
开关boolSet{
案例[假]:
返回。全部为false
案例[正确]:
返回,完全正确
违约:
返回,混合
}
}//任何出版商
.分配(给:&$boolState)
}
}
注意:.combinelatetest()
不是Combine
的一部分,但它只是我编写的一个扩展,它迭代数组中的每对发布服务器,并迭代调用它们,如first.combinelatetest(second).combinelatetest(third)
等。如果您需要比这更健壮的东西,它看起来有一个带有多个选项的CombineTestMany
扩展
此时,我的视图只执行一个@ObservedObject var viewModel:viewModel
,然后在主体中使用viewModel.boolState
。无论何时任何已启用
标志因任何原因发生更改,视图都会成功更新 只需进入SwiftUI视图@ObservedObject var proposedChange:proposedChange
。当您初始化视图时,您将执行类似于ChangeView(proposedChange:proposedChanges)
的操作。在SwiftUI中,允许您观察更改。您也可以使用.sink
,但也必须为数组中的每个值使用一个,最好将报告
移动到一个视图模型中,并以某种方式创建一个anycancelable
的动态数组,我认为这不会起作用,因为每个报告视图都有许多ProposedChange
数组@ObservedObject var proposedChange:proposedChange
只允许我观察一个我在下面添加了一些代码。你可以有一个ViewModel,它将触发所有级别的更改。你确定你需要ProposedChange
成为一个类吗?@NewDev我不确定,但我想是的。我把它做成了一个类,这样我就可以在点击切换时不必初始化一个新的结构就可以启用
。这是一个比使用ObservedObject更简单的解决方案,但不幸的是,它使启用了突变(通过切换或后台进程)由于ForEach
@AaronBrager中的传递值语义非常困难,什么是非常困难的?通过切换,它就像使用绑定一样简单,绑定是SwiftUI的核心部分。不确定你所说的后台进程是什么意思-但是如果该进程拥有数据,你可以很容易地改变一个属性,并且使用@Published
将达到类似的效果。问题是提议的更改
s