在 SwiftUI 模型对象中发布计算属性
Published computed properties in SwiftUI model objects
假设我的 SwiftUI 应用程序中有一个如下所示的数据模型:
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published var count = 0
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = []
}
我想将计算的 属性 添加到 GroupOfTallies
,类似于以下内容:
// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
return elements.reduce(0) { [=11=] + .count }
}
但是,我希望 SwiftUI 在 cumulativeCount
更改时更新视图。这会在 elements
更改(数组获得或丢失元素)或任何包含的 Tallies
对象的 count
字段更改时发生。
我研究过将其表示为 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) { [=10=] + .count }
}
}
这里有多个问题需要解决。
首先,重要的是要了解 SwiftUI 在检测到变化时会更新视图的主体,无论是在 @State
属性 中,还是从 ObservableObject
中(通过 @ObservedObject
和 @EnvironmentObject
属性 包装纸)。
在后一种情况下,这是通过 @Published
属性 或手动 objectWillChange.send()
完成的。 objectWillChange
是 ObservableObjectPublisher
发布者,可在任何 ObservableObject
.
上使用
说 IF 计算 属性 中的变化是由 together 与任何 @Published
属性 的变化 - 例如,当从某处添加另一个元素时:
elements.append(Talies())
那么不需要做任何其他事情 - SwiftUI 将重新计算观察它的视图,并将读取计算的新值 属性 cumulativeCount
.
当然,如果 Tallies
对象之一的 .count
属性 发生变化,这不会导致 elements
发生变化,因为 Tallies
是参考类型。
给出简化示例的最佳方法实际上是使其成为值类型 - struct
:
struct Tallies: Identifiable {
let id = UUID()
var count = 0
}
现在,任何 Tallies
对象的变化都会导致 elements
的变化,这将导致“观察”它的视图获得计算的新值属性。同样,不需要额外的工作。
但是,如果您坚持认为 Tallies
出于任何原因不能是值类型,那么您需要通过订阅 [=33] 来收听 Tallies
中的任何更改=] 出版商:
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = [] {
didSet {
cancellables = [] // cancel the previous subscription
elements.publisher
.flatMap { [=12=].objectWillChange }
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}
private var cancellables = Set<AnyCancellable>
var cumulativeCount: Int {
return elements.reduce(0) { [=12=] + .count } // no changes here
}
}
以上将通过以下方式订阅 elements
数组中的更改(以说明添加和删除):
- 将数组转换为每个数组元素的
Sequence
发布者
- 然后 flatMap 再次将每个数组元素,这是一个
Tallies
对象,放入其 objectWillChange
发布者
- 然后对于任何输出,调用
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 {
[=10=].objectWillChange.sink( receiveValue: objectWillChange.send)
}
}
}
var cumulativeCount: Int {
return elements.reduce(0) { [=10=] + .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")
}
}
}
}
假设我的 SwiftUI 应用程序中有一个如下所示的数据模型:
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published var count = 0
}
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = []
}
我想将计算的 属性 添加到 GroupOfTallies
,类似于以下内容:
// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
return elements.reduce(0) { [=11=] + .count }
}
但是,我希望 SwiftUI 在 cumulativeCount
更改时更新视图。这会在 elements
更改(数组获得或丢失元素)或任何包含的 Tallies
对象的 count
字段更改时发生。
我研究过将其表示为 AnyPublisher
,但我认为我对 Combine 的掌握不够好,无法使其正常工作。 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) { [=10=] + .count }
}
}
这里有多个问题需要解决。
首先,重要的是要了解 SwiftUI 在检测到变化时会更新视图的主体,无论是在 @State
属性 中,还是从 ObservableObject
中(通过 @ObservedObject
和 @EnvironmentObject
属性 包装纸)。
在后一种情况下,这是通过 @Published
属性 或手动 objectWillChange.send()
完成的。 objectWillChange
是 ObservableObjectPublisher
发布者,可在任何 ObservableObject
.
说 IF 计算 属性 中的变化是由 together 与任何 @Published
属性 的变化 - 例如,当从某处添加另一个元素时:
elements.append(Talies())
那么不需要做任何其他事情 - SwiftUI 将重新计算观察它的视图,并将读取计算的新值 属性 cumulativeCount
.
当然,如果 Tallies
对象之一的 .count
属性 发生变化,这不会导致 elements
发生变化,因为 Tallies
是参考类型。
给出简化示例的最佳方法实际上是使其成为值类型 - struct
:
struct Tallies: Identifiable {
let id = UUID()
var count = 0
}
现在,任何 Tallies
对象的变化都会导致 elements
的变化,这将导致“观察”它的视图获得计算的新值属性。同样,不需要额外的工作。
但是,如果您坚持认为 Tallies
出于任何原因不能是值类型,那么您需要通过订阅 [=33] 来收听 Tallies
中的任何更改=] 出版商:
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = [] {
didSet {
cancellables = [] // cancel the previous subscription
elements.publisher
.flatMap { [=12=].objectWillChange }
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}
private var cancellables = Set<AnyCancellable>
var cumulativeCount: Int {
return elements.reduce(0) { [=12=] + .count } // no changes here
}
}
以上将通过以下方式订阅 elements
数组中的更改(以说明添加和删除):
- 将数组转换为每个数组元素的
Sequence
发布者 - 然后 flatMap 再次将每个数组元素,这是一个
Tallies
对象,放入其objectWillChange
发布者 - 然后对于任何输出,调用
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 {
[=10=].objectWillChange.sink( receiveValue: objectWillChange.send)
}
}
}
var cumulativeCount: Int {
return elements.reduce(0) { [=10=] + .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")
}
}
}
}