SwiftUI模型对象中已发布的计算属性
假设我的SwiftUI应用程序中有一个如下所示的数据模型:SwiftUI模型对象中已发布的计算属性,swift,swiftui,combine,Swift,Swiftui,Combine,假设我的SwiftUI应用程序中有一个如下所示的数据模型: class Tallies: Identifiable, ObservableObject { let id = UUID() @Published var count = 0 } class GroupOfTallies: Identifiable, ObservableObject { let id = UUID() @Published var elements: [Tallies] = [] } // Ret
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published var count = 0
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = []
}
// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count }
}
我想向groupoftraines
添加一个类似于以下内容的计算属性:
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published var count = 0
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = []
}
// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count }
}
但是,我希望SwiftUI在cumulativeCount
更改时更新视图。当元素
发生变化(数组获得或丢失元素)或任何包含的对象的计数
字段发生变化时,都会发生这种情况
我已经研究过将它表示为一个AnyPublisher
,但我认为我对Combine的掌握还不够好,无法使它正常工作。中提到了这一点,但从中创建的AnyPublisher是基于已发布的Double
而不是已发布的Array
。如果我尝试在不进行修改的情况下使用相同的方法,cumulativeCount
仅在元素数组更改时更新,但在其中一个元素的count
属性更改时不会更新。最简单和最快的方法是使用值类型模型
这里是一个简单的演示。使用Xcode 12/iOS 14进行测试和工作
struct TestTallies: View {
@StateObject private var group = GroupOfTallies() // SwiftUI 2.0
// @ObservedObject private var group = GroupOfTallies() // SwiftUI 1.0
var body: some View {
VStack {
Text("Cumulative: \(group.cumulativeCount)")
Divider()
Button("Add") { group.elements.append(Tallies(count: 1)) }
Button("Update") { group.elements[0].count = 5 }
}
}
}
struct Tallies: Identifiable { // << make struct !!
let id = UUID()
var count = 0
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = []
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count }
}
}
结构测试计数:查看{
@StateObject private var group=groupoftraines()//SwiftUI 2.0
//@ObservedObject private var group=groupoftraines()//SwiftUI 1.0
var body:一些观点{
VStack{
文本(“累计:\(group.cumulativeCount)”)
分隔器()
按钮(“添加”){group.elements.append(计数(计数:1))}
按钮(“更新”){group.elements[0].count=5}
}
}
}
结构计数:可识别{/这里有多个问题需要解决
首先,重要的是要了解SwiftUI在检测到更改时会更新视图的主体,无论是在@State
属性中还是从ObserveObject
(通过@ObserveObject
和@EnvironmentObject
属性包装器)
在后一种情况下,这可以通过@Published
属性完成,也可以使用objectWillChange手动完成。send()
objectWillChange
是一个可观测对象发布者
发布者,可在任何可观测对象
上使用
如果计算属性中的更改与任何已发布的@属性的更改一起导致,例如,当从某处添加另一个元素时,这是一个很长的说法
elements.append(Talies())
则无需执行任何其他操作-SwiftUI将重新计算观察到它的视图,并读取计算属性的新值cumulativeCount
当然,如果其中一个TALLES
对象的.count
属性发生更改,这不会导致元素的更改,因为TALLES
是一种引用类型
给出简化示例的最佳方法实际上是将其设置为值类型-astruct
:
struct Tallies: Identifiable {
let id = UUID()
var count = 0
}
现在,任何计数
对象的更改都会导致元素的更改
,这将导致“观察”它的视图获得计算属性的新值。同样,不需要额外的工作
但是,如果您坚持认为无论出于何种原因,计数
都不能是值类型,那么您需要通过订阅其来收听计数
中的任何更改。objectWillChange
发布者:
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = [] {
didSet {
cancellables = [] // cancel the previous subscription
elements.publisher
.flatMap { $0.objectWillChange }
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}
private var cancellables = Set<AnyCancellable>
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count } // no changes here
}
}
class-groupoftraines:可识别、可观察的对象{
设id=UUID()
@已发布的变量元素:[计数]=[]{
迪塞特{
Cancelables=[]//取消以前的订阅
elements.publisher
.flatMap{$0.objectWillChange}
.sink(接收值:self.objectWillChange.send)
.store(在:&可取消项中)
}
}
private var cancelables=Set
var累积数:Int{
返回元素。reduce(0){$0+$1.count}//此处无更改
}
}
上述内容将通过以下方式订阅元素
数组中的更改(以说明添加和删除):
- 将数组转换为每个数组元素的
序列
- 然后,将每个数组元素(即
计数的对象)再次平面映射到其对象将更改发布者
- 然后,对于任何输出,调用
objectWillChange.send()
,通知观察它自身更改的视图
这与@New Dev
的答案的最后一个选项类似,但稍微短一点,本质上只是将objectWillChange
通知传递给父对象:
import Combine
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published var count = 0
func increase() {
count += 1
}
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
var sinks: [AnyCancellable] = []
@Published var elements: [Tallies] = [] {
didSet {
sinks = elements.map {
$0.objectWillChange.sink( receiveValue: objectWillChange.send)
}
}
}
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count }
}
}
SwiftUI演示:
struct ContentView: View {
@ObservedObject
var group: GroupOfTallies
init() {
let group = GroupOfTallies()
group.elements.append(contentsOf: [Tallies(), Tallies()])
self.group = group
}
var body: some View {
VStack(spacing: 50) {
Text( "\(group.cumulativeCount)")
Button( action: group.elements.first!.increase) {
Text( "Increase first")
}
Button( action: group.elements.last!.increase) {
Text( "Increase last")
}
}
}
}
这是一个问题,但你也需要接收你以后添加的元素。@CasperZandbergen是你对最后一种方法的评论吗?你说的接收你以后添加的元素是什么意思?如果有什么问题的话,问题是每次添加都会复制发布者。我想我已经修复了它。最后一种方法不是我建议解决的方法OP的问题-就在这里,我使用的数据存储依赖于使用对象,因此您在最后提到的方法似乎是解决此问题的最佳方法。尽管解决方案有一个保留周期,groupoftraines
将永远不会发布,除非手动将elements
设置为[]
。.sink
闭包需要一个[弱自我]
注释。