根据观察到的变量的变化更新@Published 变量
Updating a @Published variable based on changes in an observed variable
我有一个可以观察到的AppState:
class AppState: ObservableObject {
private init() {}
static let shared = AppState()
@Published fileprivate(set) var isLoggedIn = false
}
视图模型应根据状态决定显示哪个视图 (isLoggedIn
):
class HostViewModel: ObservableObject, Identifiable {
enum DisplayableContent {
case welcome
case navigationWrapper
}
@Published var containedView: DisplayableContent = AppState.shared.isLoggedIn ? .navigationWrapper : .welcome
}
最终 HostView
观察 containedView
属性 并基于它显示正确的视图。
我的问题是上面的代码没有观察到 isLoggedIn
,我似乎无法找到一种方法来做到这一点。我很确定有一个简单的方法,但经过 4 小时的反复试验,我希望这里的社区可以帮助我。
当您将 ObservedObject
添加到视图时,SwiftUI 会为 objectWillChange
发布者添加一个接收器,您需要执行相同的操作。由于 objectWillChange
是在 isLoggedIn
更改之前发送的,因此添加一个在其 didSet
中发送的发布者可能是个好主意。由于您对初始值和更改感兴趣,因此 CurrentValueSubject<Bool, Never>
可能是最好的。然后,在您的 HostViewModel
中,您需要订阅 AppState
的新发布者并使用发布的值更新 containedView
。使用 assign
会导致引用循环,所以 sink
和对 self
的弱引用是最好的。
没有代码,但非常简单。最后一个要注意的陷阱是将 sink
的返回值保存到 AnyCancellable?
中,否则您的订阅者将消失。
DISCLAIMER:
It is not full solution to the problem, it won't trigger objectWillChange
, so it's useless for ObservableObject
. But it may be useful for some related problems.
主要想法是创建 propertyWrapper
,它将根据链接 Publisher
:
中的变化更新 属性 值
@propertyWrapper
class Subscribed<Value, P: Publisher>: ObservableObject where P.Output == Value, P.Failure == Never {
private var watcher: AnyCancellable?
init(wrappedValue value: Value, _ publisher: P) {
self.wrappedValue = value
watcher = publisher.assign(to: \.wrappedValue, on: self)
}
@Published
private(set) var wrappedValue: Value {
willSet {
objectWillChange.send()
}
}
private(set) lazy var projectedValue = self.$wrappedValue
}
用法:
class HostViewModel: ObservableObject, Identifiable {
enum DisplayableContent {
case welcome
case navigationWrapper
}
@Subscribed(AppState.shared.$isLoggedIn.map({ [=11=] ? DisplayableContent.navigationWrapper : .welcome }))
var contained: DisplayableContent = .welcome
// each time `AppState.shared.isLoggedIn` changes, `contained` will change it's value
// and there's no other way to change the value of `contained`
}
工作解决方案:
在使用 Combine 两周后,我现在再次修改了我以前的解决方案(请参阅编辑历史记录),这是我现在能想到的最好的解决方案。这仍然不完全是我的想法,因为 contained
不是订阅者和发布者同时,但我认为 AnyCancellable
总是需要的。如果有人知道实现我的愿景的方法,请告诉我。
class HostViewModel: ObservableObject, Identifiable {
@Published var contained: DisplayableContent
private var containedUpdater: AnyCancellable?
init() {
self.contained = .welcome
setupPipelines()
}
private func setupPipelines() {
self.containedUpdater = AppState.shared.$isLoggedIn
.map { [=10=] ? DisplayableContent.mainContent : .welcome }
.assign(to: \.contained, on: self)
}
}
extension HostViewModel {
enum DisplayableContent {
case welcome
case mainContent
}
}
订阅嵌入式 ObservedObject
中 @Published
变量更改的通用解决方案是将 objectWillChange
通知传递给父对象。
示例:
import Combine
class Parent: ObservableObject {
@Published
var child = Child()
var sink: AnyCancellable?
init() {
sink = child.objectWillChange.sink(receiveValue: objectWillChange.send)
}
}
class Child: ObservableObject {
@Published
var counter: Int = 0
func increase() {
counter += 1
}
}
SwiftUI 演示使用:
struct ContentView: View {
@ObservedObject
var parent = Parent()
var body: some View {
VStack(spacing: 50) {
Text( "\(parent.child.counter)")
Button( action: parent.child.increase) {
Text( "Increase")
}
}
}
}
我有一个可以观察到的AppState:
class AppState: ObservableObject {
private init() {}
static let shared = AppState()
@Published fileprivate(set) var isLoggedIn = false
}
视图模型应根据状态决定显示哪个视图 (isLoggedIn
):
class HostViewModel: ObservableObject, Identifiable {
enum DisplayableContent {
case welcome
case navigationWrapper
}
@Published var containedView: DisplayableContent = AppState.shared.isLoggedIn ? .navigationWrapper : .welcome
}
最终 HostView
观察 containedView
属性 并基于它显示正确的视图。
我的问题是上面的代码没有观察到 isLoggedIn
,我似乎无法找到一种方法来做到这一点。我很确定有一个简单的方法,但经过 4 小时的反复试验,我希望这里的社区可以帮助我。
当您将 ObservedObject
添加到视图时,SwiftUI 会为 objectWillChange
发布者添加一个接收器,您需要执行相同的操作。由于 objectWillChange
是在 isLoggedIn
更改之前发送的,因此添加一个在其 didSet
中发送的发布者可能是个好主意。由于您对初始值和更改感兴趣,因此 CurrentValueSubject<Bool, Never>
可能是最好的。然后,在您的 HostViewModel
中,您需要订阅 AppState
的新发布者并使用发布的值更新 containedView
。使用 assign
会导致引用循环,所以 sink
和对 self
的弱引用是最好的。
没有代码,但非常简单。最后一个要注意的陷阱是将 sink
的返回值保存到 AnyCancellable?
中,否则您的订阅者将消失。
DISCLAIMER:
It is not full solution to the problem, it won't trigger
objectWillChange
, so it's useless forObservableObject
. But it may be useful for some related problems.
主要想法是创建 propertyWrapper
,它将根据链接 Publisher
:
@propertyWrapper
class Subscribed<Value, P: Publisher>: ObservableObject where P.Output == Value, P.Failure == Never {
private var watcher: AnyCancellable?
init(wrappedValue value: Value, _ publisher: P) {
self.wrappedValue = value
watcher = publisher.assign(to: \.wrappedValue, on: self)
}
@Published
private(set) var wrappedValue: Value {
willSet {
objectWillChange.send()
}
}
private(set) lazy var projectedValue = self.$wrappedValue
}
用法:
class HostViewModel: ObservableObject, Identifiable {
enum DisplayableContent {
case welcome
case navigationWrapper
}
@Subscribed(AppState.shared.$isLoggedIn.map({ [=11=] ? DisplayableContent.navigationWrapper : .welcome }))
var contained: DisplayableContent = .welcome
// each time `AppState.shared.isLoggedIn` changes, `contained` will change it's value
// and there's no other way to change the value of `contained`
}
工作解决方案:
在使用 Combine 两周后,我现在再次修改了我以前的解决方案(请参阅编辑历史记录),这是我现在能想到的最好的解决方案。这仍然不完全是我的想法,因为 contained
不是订阅者和发布者同时,但我认为 AnyCancellable
总是需要的。如果有人知道实现我的愿景的方法,请告诉我。
class HostViewModel: ObservableObject, Identifiable {
@Published var contained: DisplayableContent
private var containedUpdater: AnyCancellable?
init() {
self.contained = .welcome
setupPipelines()
}
private func setupPipelines() {
self.containedUpdater = AppState.shared.$isLoggedIn
.map { [=10=] ? DisplayableContent.mainContent : .welcome }
.assign(to: \.contained, on: self)
}
}
extension HostViewModel {
enum DisplayableContent {
case welcome
case mainContent
}
}
订阅嵌入式 ObservedObject
中 @Published
变量更改的通用解决方案是将 objectWillChange
通知传递给父对象。
示例:
import Combine
class Parent: ObservableObject {
@Published
var child = Child()
var sink: AnyCancellable?
init() {
sink = child.objectWillChange.sink(receiveValue: objectWillChange.send)
}
}
class Child: ObservableObject {
@Published
var counter: Int = 0
func increase() {
counter += 1
}
}
SwiftUI 演示使用:
struct ContentView: View {
@ObservedObject
var parent = Parent()
var body: some View {
VStack(spacing: 50) {
Text( "\(parent.child.counter)")
Button( action: parent.child.increase) {
Text( "Increase")
}
}
}
}