ios - @Published 和 .assign 对值更新没有反应
问题描述
SwiftUI 和在这里结合菜鸟,我在操场上隔离了我遇到的问题。这里是游乐场。
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
}
}
解决方案
我更喜欢使用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)
}
请注意,视图的状态仅在视图层次结构中链接,在 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()
}
}
推荐阅读
- angular - 角度通过控制器通过类扩展传递值
- javascript - Angularjs - 如何使用 angularjs 实现拖放列?
- typescript - 错误:“未捕获(承诺):[object Object]
- spring - 如何将下面给出的 XML bean 转换为 java bean,同样的例子将不胜感激
- javascript - 如何以不同的颜色显示文本的某些部分,即@和}之间的数据应该是不同的颜色
- kubernetes - kubernetes 中的 Janusgraph 无法连接到作为另一个服务运行的 Cassandra
- node.js - 如何在 Node JS 中将 .flv 文件转换为 .mp4
- c# - 跨多个项目的 EntityFramework 上下文或在两个上下文之间加入
- python - 如何提取MAC id?
- java - 第二个参数类型错误。找到'java.lang.String',需要'android.location.Location'