【问题标题】:How to chain ObservableObject?如何链接 ObservableObject?
【发布时间】:2020-08-15 12:26:14
【问题描述】:

我有一个可以保存图像的游戏对象。每当找到游戏的图像 URL 时,都应该创建一个新的 GameImage-object 实例。然后它将获取图像并填充 UIImage 属性。发生这种情况时,应更新 UI 以显示图像。

class Game: ObservableObject {
    @Published var image: GameImage?
} 

class GameImage: ObservableObject {
    let url: URL
    @Published var image: UIImage?
    
    private var cancellable: AnyCancellable?
    
    init(url: URL) {
        self.url = url
    }
    
    func fetch() {
        self.cancellable = URLSession.shared.dataTaskPublisher(for: self.url)
            .map { UIImage(data: $0.data) }
            .replaceError(with: nil)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (image) in
                guard let self = self else { return }
                self.image = image
                print(self.url)
                print(self.image)
            })
    }
    
    func cancel() {
        cancellable?.cancel()
    }
    
    deinit {
        cancel()
    }
}

struct ContentView: View {
    
    @StateObject var game = Game()

    var body: some View {
        VStack {
            if let image = game.image?.image {
                Image(uiImage: image)
            } else {
                Text("No image.")
            }
        }
        .onAppear(perform: {
            guard let gameImageURL = URL(string: "https://cf.geekdo-images.com/itemrep/img/oVEpcbtyWkJjIjk1peTJo6hI1yk=/fit-in/246x300/pic4884996.jpg") else { return }
            game.image = GameImage(url: gameImageURL)
            game.image!.fetch()
        })
    }
}

问题是。获取完成后,调试控制台将显示该图像包含一个 UIImage。但是 UI 不会更新以显示图像。我在这里错过了什么?

【问题讨论】:

  • 你会显示依赖于这个视图模型的 UI 吗?
  • 我不确定你的意思。游戏实例game 应该包含单一的事实来源。游戏对象还可以包含其他属性,例如我从该示例中删除的名称,以免使事情变得过于复杂。每当图像可用时,它就应该呈现它,以这种方式它是依赖的。

标签: asynchronous swiftui combine


【解决方案1】:

有比链接ObservableObject 更简单的解决方案,只需将依赖部分分离到独立的子视图中......所有的都会自动运行。

这是可能的方法。使用 Xcode 12 / iOS 14 测试。

struct ContentView: View {

    @StateObject var game = Game()

    var body: some View {
        VStack {
            if nil != game.image {
                GameImageView(vm: game.image!)
            }
        }
        .onAppear(perform: {
            guard let gameImageURL = URL(string: "https://cf.geekdo-images.com/itemrep/img/oVEpcbtyWkJjIjk1peTJo6hI1yk=/fit-in/246x300/pic4884996.jpg") else { return }
            game.image = GameImage(url: gameImageURL)
            game.image!.fetch()
        })
    }
}

struct GameImageView: View {
    @ObservedObject var vm: GameImage

    var body: some View {
        if let image = vm.image {
            Image(uiImage: image)
        } else {
            Text("No image.")
        }
    }
}

【讨论】:

  • 不强制解包选项不是更好吗?不能是game.image?.fetch()吗?我知道这是问题所在,但您可以随时改进。
  • 是的,虽然在这个例子中我将 game.image 设置在上面一行,所以它很安全。
  • 我刚刚测试了代码,它工作正常。我仍在尝试围绕这个概念展开思考。
  • @Mark 当然,但作为一个好习惯,我建议不要强制解包选项。您可以稍后将此行复制到其他地方,然后忘记它。
猜你喜欢
  • 2021-07-06
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 2020-12-21
  • 1970-01-01
  • 2019-12-20
  • 2023-03-06
  • 1970-01-01
相关资源
最近更新 更多