我觉得我可以理解为什么我正在做的事情不起作用,但我仍然试图围绕 Combine 和 SwiftUI 进行思考,所以欢迎在这里提供任何帮助。
请考虑以下示例:
在 UserDefault 中存储一些字符串的单一视图应用,并使用这些字符串显示一些文本标签。有三个按钮,一个用于更新标题,另一个用于将两个 UserDefaults 存储的字符串更新为随机字符串。
该视图是哑渲染器视图,标题字符串直接存储在 ObservableObject 视图模型中。视图模型具有一个已发布的属性,该属性包含对 UserSettings 类的引用,该类实现属性包装器以将用户定义的字符串存储到 UserDefaults。
观察:
• 点击"设置新标题"可正确更新视图以显示新值
• 点击任一"设置用户值"按钮确实会在内部更改值,但视图不会刷新。 如果在其中一个按钮之后点击"设置新标题",则当视图主体为标题更改重新生成时,将显示新值。
视图:
import SwiftUI
struct ContentView: View {
@ObservedObject var model = ViewModel()
var body: some View {
VStack {
Text(model.title).font(.largeTitle)
Form {
Section {
Text(model.settings.UserValue1)
Text(model.settings.UserValue2)
}
Section {
Button(action: {
self.model.title = "Updated Title"
}) { Text("Set A New Title") }
Button(action: {
self.model.settings.UserValue1 = "(Int.random(in: 1...100))"
}) { Text("Set User Value 1 to Random Integer") }
Button(action: {
self.model.settings.UserValue2 = "(Int.random(in: 1...100))"
}) { Text("Set User Value 2 to Random Integer") }
}
Section {
Button(action: {
self.model.settings.UserValue1 = "Initial Value One"
self.model.settings.UserValue2 = "Initial Value Two"
self.model.title = "Initial Title"
}) { Text("Reset All") }
}
}
}
}
}
视图模型:
import Combine
class ViewModel: ObservableObject {
@Published var title = "Initial Title"
@Published var settings = UserSettings()
}
用户设置模型:
import Foundation
import Combine
@propertyWrapper struct DefaultsWritable<T> {
let key: String
let value: T
init(key: String, initialValue: T) {
self.key = key
self.value = initialValue
}
var wrappedValue: T {
get { return UserDefaults.standard.object(forKey: key) as? T ?? value }
set { return UserDefaults.standard.set(newValue, forKey: key) }
}
}
final class UserSettings: NSObject, ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
@DefaultsWritable(key: "UserValue", initialValue: "Initial Value One") var UserValue1: String {
willSet {
objectWillChange.send()
}
}
@DefaultsWritable(key: "UserBeacon2", initialValue: "Initial Value Two") var UserValue2: String {
willSet {
objectWillChange.send()
}
}
}
当我在用户设置中willSet { objectWillChange.send() }
上放置断点时,我看到 objectWillChange 消息在我期望它时发送给发布者,以便告诉我问题可能是视图或视图模型未正确订阅它。我知道如果我在视图上将用户设置作为@ObservedObject
,这将起作用,但我觉得这应该在带有 Combine 的视图模型中完成。
我在这里错过了什么?我相信这真的很明显...
ObsesrvedObject
侦听@Published
属性的变化,但不侦听更深层次的内部发布者,所以下面的思路是加入内部发布者,这是PassthroughSubject
,与@Published var settings
,以表明后者已经更新。
与Xcode 11.2/iOS 13.2兼容
唯一需要的更改是ViewModel
...
class ViewModel: ObservableObject {
@Published var title = "Initial Title"
@Published var settings = UserSettings()
private var cancellables = Set<AnyCancellable>()
init() {
self.settings.objectWillChange
.sink { _ in
self.objectWillChange.send()
}
.store(in: &cancellables)
}
}