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

标签 ios swift swiftui combine publisher

SwiftUI 和结合菜鸟在这里,我在 Playground 上隔离了我遇到的问题。这里是 Playground 。

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)
}

当我运行 Playground 时,会发生以下情况:

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))

正如您所看到的,存在两个问题:

  • 紧凑 map 闭包仅在订阅时调用一次,但在运行调度时不会调用

  • 赋值运算符永远不会被调用

过去几个小时我一直在尝试解决这个问题,但没有成功。也许对 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
    }
}

最佳答案

我更喜欢使用下面的 ObservableObject/ObservedObject 模式,但其他变体也是可能的(进一步提供)

全部使用 Xcode 11.2/iOS 13.2 进行测试

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)
    }
}

替代:

以下内容修复了您的情况(如果您不存储订阅者,发布者将立即取消)

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

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

请注意, View 的状态仅在 View 层次结构中链接,在 Playground 中,就像您所做的那样,它仅保留初始值。

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

struct TestSwiftUIView: View {

    @State private var viewModel: String = "test"

    var body: some View {
        Text("\(viewModel)")
            .onReceive(publisher) { value in
                self.viewModel = value
            }
    }

    let publisher: AnyPublisher<String, Never>
    init(textContainer: ReactiveContainer<String>) {

        publisher = textContainer.$containedValue.compactMap {
            print("compact map \($0)")
            return $0
        }.eraseToAnyPublisher()
    }
}

关于ios - @Published 和 .assign 对值更新没有反应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60667976/

相关文章:

ios - xCode 相同的应用程序自动不同的WS

ios - NSNotification 多个观察者,只有一个被调用

ios - MonkeyTalk 错误报告到 .txt/excel 文件

swift - 如何从 CIRowAverage 过滤器中获取值?

ios - SwiftUI:minimumScaleFactor 没有均匀地应用于堆栈元素

ios - 对键盘显示和隐藏设置动画自动布局约束

swift - 按下后在 uiviewcontroller 内部不调用 deinit - Swift

ios - 防止 Xcode 重建更改配置

macos - SwiftUI 2 - 如何防止顶部部分在 macOS 和 iPadOS 中的列表中关闭

SwiftUI:是否基于属性值设置 View 可见性?