@Published和.assign对值更新没有反应

时间:2020-03-13 09:33:34

标签: ios swift swiftui combine publisher

SwiftUI和Combine noob在这里,我在操场上隔离了遇到的问题。这是操场。

final class ReactiveContainer<T: Equatable> {
    @Published var containedValue: T?
}

class AppContainer {
    static let shared = AppContainer()

    let text = ReactiveContainer<String>()
}

struct TestSwiftUIView: View {

    @State private var viewModel = "test"

    var body: some View {
        Text("\(viewModel)")
    }

    init(textContainer: ReactiveContainer<String>) {

        textContainer.$containedValue.compactMap {
            print("compact map \($0)")
            return $0
        }.assign(to: \.viewModel, on: self)
    }
}

AppContainer.shared.text.containedValue = "init"


var testView = TestSwiftUIView(textContainer: AppContainer.shared.text)
print(testView)

print("Executing network request")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    AppContainer.shared.text.containedValue = "Hello world"
    print(testView)
}

我在操场上跑的时候是这样的:

compact map Optional("init")
TestSwiftUIView(_viewModel: SwiftUI.State<Swift.String>(_value: "test", _location: nil))
Executing network request
TestSwiftUIView(_viewModel: SwiftUI.State<Swift.String>(_value: "test", _location: nil))

如您所见,那里有两个问题:

  • 紧凑型地图关闭仅在订阅时调用一次,而在调度运行时不会调用

  • 从不调用赋值运算符

在过去的几个小时中,我一直试图解决此问题,但没有成功。也许某人在SwiftUI / Combine中有最高级的知识可以帮助我,谢谢!

编辑

这是可行的解决方案:

struct ContentView: View {

    @State private var viewModel = "test"
    let textContainer: ReactiveContainer<String>

    var body: some View {
        Text(viewModel).onReceive(textContainer.$containedValue) { (newContainedValue) in
            self.viewModel = newContainedValue ?? ""
        }
    }

    init(textContainer: ReactiveContainer<String>) {
        self.textContainer = textContainer
    }
}

3 个答案:

答案 0 :(得分:1)

我宁愿使用下面的/abc/xyz模式,但也可以使用其他变体(进一步提供)

所有内容均通过Xcode 11.2 / iOS 13.2进行了测试

ObservableObject/ObservedObject

备用:

以下内容可解决您的问题(如果您不存储订户,发布者将立即被取消)

final class ReactiveContainer<T: Equatable>: ObservableObject {
    @Published var containedValue: T?
}

struct TestSwiftUIView: View {

    @ObservedObject var vm: ReactiveContainer<String>

    var body: some View {
        Text("\(vm.containedValue ?? "<none>")")
    }

    init(textContainer: ReactiveContainer<String>) {
        self._vm = ObservedObject(initialValue: textContainer)
    }
}

请注意,视图的状态仅在视图层次结构中链接,就像在Playground中一样,它仅包含初始值。

另一种可能更适合SwiftUI层次结构的方法是

private var subscriber: AnyCancellable?
init(textContainer: ReactiveContainer<String>) {

    subscriber = textContainer.$containedValue.compactMap {
        print("compact map \($0)")
        return $0
    }.assign(to: \.viewModel, on: self)
}

答案 1 :(得分:0)

我将保存对AppContainer的引用。

struct TestSwiftUIView: View {

    @State private var viewModel = "test"

    ///I just added this
    var textContainer: AnyCancellable?

    var body: some View {
        Text("\(viewModel)")
    }

    init(textContainer: ReactiveContainer<String>) {

        self.textContainer = textContainer.$containedValue.compactMap {
            print("compact map \(String(describing: $0))")
            return $0
        }.assign(to: \.viewModel, on: self)
    }
}

compact map Optional("init")
TestSwiftUIView(_viewModel: SwiftUI.State<Swift.String>(_value: "test", _location: nil), textContainer: Optional(Combine.AnyCancellable))
Executing network request
compact map Optional("Hello")
TestSwiftUIView(_viewModel: SwiftUI.State<Swift.String>(_value: "test", _location: nil), textContainer: Optional(Combine.AnyCancellable))

答案 2 :(得分:0)

我们不使用Combine 在视图之间移动数据,SwiftUI 已经内置了对此的支持。主要问题是您将 TestSwiftUIView 视为一个类,但它是一个结构,即一个值。最好将视图简单地视为要显示的数据。当数据发生变化时,SwiftUI 会一遍又一遍地创建这些数据结构。所以解决办法很简单:

struct ContentView: View {

    let text: String

    var body: some View { // only called if text is different from last time ContentView was created in a parent View's body.
        Text(text)
    }
}

父 body 方法可以一遍又一遍地调用 ContentView(text:"Test"),但是 ContentView body 方法仅在 let text 与上次不同时才会被 SwiftUI 调用,例如ContentView(text:"Test2")。我认为这是您尝试使用 Combine 重新创建的内容,但这是不必要的,因为 SwiftUI 已经做到了。