根据观察变量的变化更新@Published变量

时间:2019-10-01 14:58:06

标签: swift combine

我有一个可以观察到的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小时的反复试验,我希望这里的社区能够为我提供帮助。

4 个答案:

答案 0 :(得分:2)

@minMonthlyPrice添加到视图时,SwiftUI将为ObservedObject发布者添加一个接收者,您需要执行相同的操作。由于objectWillChange是在objectWillChange更改之前发送的,因此最好添加一个发送其isLoggedIn的发布者。当您对初始值和更改感兴趣时,didSet可能是最好的。然后,您需要在CurrentValueSubject<Bool, Never>中订阅HostViewModel的新发布者,并使用发布的值更新AppState。使用containedView可能会导致参考周期,因此对assign的引用较弱的sink最好。

没有代码,但是非常简单。最后要注意的陷阱是将返回的值从self保存到sink,否则您的订户将消失。

答案 1 :(得分:1)

  

免责声明

     

这不是解决问题的完整方法,它不会触发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`
}

答案 2 :(得分:1)

工作解决方案:

在使用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 { $0 ? DisplayableContent.mainContent : .welcome }
            .assign(to: \.contained, on: self)
    }

}

extension HostViewModel {

    enum DisplayableContent {
        case welcome
        case mainContent
    }

}

答案 3 :(得分:1)

订阅嵌入式@PublishedObservedObject变量的更改的通用解决方案是将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")
      }
    }
  }
}