在对观察对象的已发布属性进行异步更改时,SwiftUI View不会更新

问题描述

我有以下SwiftUI视图:

struct ProductView: View {
@Observedobject var productviewmodel: Productviewmodel


var body: some View {
    vstack {
        ZStack(alignment: .top) {
            if(self.productviewmodel.product != nil) {
                URLImage(url: self.productviewmodel.product!.imageurl,itemColor: self.productviewmodel.selectedColor)
            }
            else {
                Image("loading")
            } 
        }
    }
}

观察到Productviewmodel

class Productviewmodel: ObservableObject {

@Published var selectedColor: UIColor = .white

@Published var product: Product?


private var cancellable: AnyCancellable!

init(productFuture: Future<Product,Never>) {

    self.cancellable = productFuture.sink(receiveCompletion: { comp in
            print(comp)
        },receiveValue: { product in
            
            self.product = product
            print(self.product)  // this prints the expected product. The network call works just fine


        })


}

Product是一个Swift结构,其中包含多个字符串属性

struct Product {
    let id: String
    let imageurl: String
    let price: String

}

它是从远程API获取的。进行提取的服务将返回合并未来,并将其传递给视图模型,如下所示:

let productFuture = retrieveProduct(productID: "1")
let productVM = Productviewmodel(productFuture: productFuture)
let productView = ProductView(productviewmodel: productviewmodel)


func retrieveProduct(productID: String) -> Future<Product,Never>{
    
    let future = Future<Product,Never> { promise in

    //  networking logic that fetches the remote product,once it finishes the success callback is invoked


        promise(.success(product))
    }

    return future
}

为了简洁起见,我已经排除了网络连接和错误处理逻辑,因为它与当前情况无关。要尽可能快地重现此代码,只需使用一些虚拟值初始化一个模拟产品,然后将其传递给成功回调,并延迟如下:

let mockproduct = Product(id: "1",imageurl: "https://exampleurl.com",price: "$10")

dispatchQueue.main.asyncAfter(deadline: .Now() + 2.0,execute: {
    promise(.success(mockproduct))
})

产品通过网络到达后,便被分配给已发布的产品属性提取工作正常,并且将正确的值分配给已发布的属性。显然,这是在视图创建后发生的,因为网络调用需要一些时间。但是,即使更改了已发布的对象,视图也永远不会更新。

当我直接通过View Model初始值设定项而不是将来传递产品时,它会按预期工作,并且视图显示正确的产品。

关于在通过合并未来异步更新视图时视图为何不对视图模型状态变化做出反应的任何建议?

编辑:当我问这个问题时,我将Productviewmodel + ProductView嵌套在另一个视图中。因此,基本上productview只是较大的CategoryView的一部分。 Categoryviewmodel通过专用方法初始化了Productviewmodel和ProductView:

func createProductView() -> AnyView {
    let productVM = productviewmodels[productIndex]
    return AnyView(ProductView(productviewmodel: productVM))
}

然后,每次更新时CategoryView都会调用它。我想这会使嵌套的Productviewmodel中的Published变量无法正确更新,因为从CategoryView向下的视图层次结构在每次更新时都会重新构建。因此,每次新更新都会调用createProductView方法,从而对ProductView + Productviewmodel进行了全新的初始化。

也许有更多SwiftUI经验的人可以对此发表评论

在嵌套视图中嵌套可观察对象通常不是一个好主意,还是有办法使这项工作不是反模式的?

如果不是,当您拥有各自具有各自状态的嵌套视图时,通常如何解决此问题?

解决方法

我一直在迭代这样的模式,以找到我认为最有效的模式。不知道问题出在哪里。我的直觉表明,SwiftUI无法在!= nil部分进行更新。

这是我一直在使用的模式,一直在起作用。

  1. 在网络逻辑中定义状态的枚举
public enum NetworkingModelViewState {
    case loading
    case hasData
    case noResults
    case error
}
  1. 将枚举作为变量添加到“视图模型”中
class ProductViewModel: ObservableObject {

    @Published public var state: NetworkingModelViewState = .loading

}
  1. 在联网过程中更新状态
    self.cancellable = productFuture.sink(receiveCompletion: { comp in
            print(comp)
        },receiveValue: { product in
            
            self.product = product
            self.state = NetworkingModelViewState.hasData
            print(self.product)  // this prints the expected product. The network call works just fine


        })
  1. 现在基于Enum值在您的SwiftUI中做出决定
            if(self.productViewModel.state == NetworkingModelViewState.hasData) {
                URLImage(url: self.productViewModel.product!.imageurl,itemColor: self.productViewModel.selectedColor)
            }
            else {
                Image("loading")
            }

注意事项〜调试声明性框架非常困难。它们功能强大,我们应该继续学习它们,但要注意被卡住。太过迅速的SwiftUI迫使我不得不真正考虑MVVM。我的收获是,您确实需要分离出控制UI的所有可能变量。您不应在读​​取变量之外依赖检查。 Combine future模式存在内存泄漏,Apple会修复下一个版本。另外,您将可以在SwiftUI下一个版本中进行切换。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...