通过另一个 ObObject 传递一个 ObservableObject 模型?
Passing an ObservableObject model through another ObObject?
我觉得我能理解为什么我正在做的事情不起作用,但我仍在努力思考 Combine 和 SwiftUI,所以欢迎任何帮助。
考虑这个例子:
在 UserDefaults 中存储一些字符串并使用这些字符串显示一些文本标签的单视图应用程序。共有三个按钮,一个用于更新标题,另一个用于将两个 UserDefaults-stored 字符串更新为随机字符串。
该视图是一个哑渲染器视图,标题字符串直接存储在 ObservableObject 视图模型中。视图模型有一个已发布的 属性,其中包含对 UserSettings class 的引用,该引用实现 属性 包装器以将用户定义的字符串存储到 UserDefaults。
观察:
• 点击 "Set A New Title" 正确更新视图以显示新值
• 点击任一 "Set User Value" 按钮确实会在内部更改值,但视图不会刷新。
如果在这些按钮之一之后点击 "Set A New Title",则当视图 body 重建标题更改时会显示新值。
查看:
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()
}
}
}
当我在 UserSettings 中的 willSet { objectWillChange.send() }
上设置断点时,我看到 objectWillChange 消息在我期望的时候发送给了发布者,这告诉我问题很可能是视图或视图模型没有正确订阅它。我知道如果我在视图上将 UserSettings 作为 @ObservedObject
,这会起作用,但我觉得这应该在带有 Combine 的视图模型中完成。
我在这里错过了什么?我确定这真的很明显...
ObsesrvedObject
监听 @Published
属性 的变化,而不是更深层次的内部发布者,所以下面的想法是加入内部发布者,即 PassthroughSubject
,用@Published var settings
表示后者更新了
Tested with 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)
}
}
我觉得我能理解为什么我正在做的事情不起作用,但我仍在努力思考 Combine 和 SwiftUI,所以欢迎任何帮助。
考虑这个例子:
在 UserDefaults 中存储一些字符串并使用这些字符串显示一些文本标签的单视图应用程序。共有三个按钮,一个用于更新标题,另一个用于将两个 UserDefaults-stored 字符串更新为随机字符串。
该视图是一个哑渲染器视图,标题字符串直接存储在 ObservableObject 视图模型中。视图模型有一个已发布的 属性,其中包含对 UserSettings class 的引用,该引用实现 属性 包装器以将用户定义的字符串存储到 UserDefaults。
观察:
• 点击 "Set A New Title" 正确更新视图以显示新值
• 点击任一 "Set User Value" 按钮确实会在内部更改值,但视图不会刷新。 如果在这些按钮之一之后点击 "Set A New Title",则当视图 body 重建标题更改时会显示新值。
查看:
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()
}
}
}
当我在 UserSettings 中的 willSet { objectWillChange.send() }
上设置断点时,我看到 objectWillChange 消息在我期望的时候发送给了发布者,这告诉我问题很可能是视图或视图模型没有正确订阅它。我知道如果我在视图上将 UserSettings 作为 @ObservedObject
,这会起作用,但我觉得这应该在带有 Combine 的视图模型中完成。
我在这里错过了什么?我确定这真的很明显...
ObsesrvedObject
监听 @Published
属性 的变化,而不是更深层次的内部发布者,所以下面的想法是加入内部发布者,即 PassthroughSubject
,用@Published var settings
表示后者更新了
Tested with 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)
}
}