我有一个可以观察的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 小时的尝试和错误,我希望这里的社区可以帮助我。
工作解决方案:
使用合并两周后,我现在再次修改了之前的解决方案(请参阅编辑历史记录),这是我现在能想到的最好的解决方案。这仍然不完全是我的想法,因为
contained
不同时是订阅者和发布者,但我认为 AnyCancellable
始终是需要的。如果有人知道实现我的愿景的方法,请仍然告诉我。
class HostViewModel: ObservableObject, Identifiable {
@Published var contained: DisplayableContent
init() {
self.contained = .welcome
setupPipelines()
}
private func setupPipelines() {
AppState.shared.$isLoggedIn
.map { $0 ? DisplayableContent.mainContent : .welcome }
.assign(to: &$contained)
}
}
extension HostViewModel {
enum DisplayableContent {
case welcome
case mainContent
}
}
免责声明:
这不是问题的完整解决方案,它不会触发
,所以对objectWillChange
没有用。但它可能对一些相关问题有用。ObservableObject
主要思想是创建
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({ $0 ? 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`
}
当您将
ObservedObject
添加到视图时,SwiftUI 会为 objectWillChange
发布者添加接收器,您需要执行相同的操作。由于 objectWillChange
在 isLoggedIn
更改之前发送,因此添加一个发送其 didSet
的发布者可能是一个想法。由于您对初始值以及更改感兴趣,因此 CurrentValueSubject<Bool, Never>
可能是最好的。然后,在您的 HostViewModel
中,您需要订阅 AppState
的新发布者并使用发布的值更新 containedView
。使用 assign
可能会导致引用循环,因此 sink
对 self
的弱引用是最好的。
没有代码,但非常简单。要注意的最后一个陷阱是将返回的值从
sink
保存到 AnyCancellable?
,否则您的订阅者将会消失。
订阅嵌入
@Published
中 ObservedObject
变量更改的通用解决方案是将 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")
}
}
}
}