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