首页 > 解决方案 > 使用 SwiftUI 和 Combine 自动构建和更新列表

问题描述

我开始学习 SwiftUI 开发,我正在制作我的第一个基本的基于 SwiftUI 的新闻应用程序,我计划开源,但我目前陷入困境。我一直在阅读 Apple 的文档并查看有关如何使用 combine 等自动处理 SwiftUI 中的数据更改的示例。我找到了一篇文章,假设会自动更新列表。我无法看到任何即时的数据更改或正在记录的任何内容。

我使用与 NewsAPI 相同的结构,但作为示例,我已将其上传到 GitHub repo。我做了一个小项目并尝试更新我的存储库中的数据并尝试查看我的数据中所做的任何更改。老实说,我正在尽我最大的努力,并且可以真正使用一些指针或更正我的错误可能是什么。我认为我的困惑在于@ObservedObject以及@Published如何处理我的内容视图中的任何更改。这篇文章没有显示他们为处理数据更改所做的任何事情,所以也许我遗漏了什么?

import Foundation
import Combine

struct News : Codable {
    var articles : [Article]
}

struct Article : Codable,Hashable {
    let description : String?
    let title : String?
    let author: String?
    let source: Source
    let content: String?
    let publishedAt: String?
}

struct Source: Codable,Hashable {
    let name: String?
}


class NewsData: ObservableObject {
    @Published var news: News = News(articles: [])
    
    init() {
        guard let url = URL(string: "https://raw.githubusercontent.com/ca13ra1/data/main/data.json") else { return }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let response = try? JSONDecoder().decode(News.self, from: data) {
                    DispatchQueue.main.async() {
                        self.news = response
                        print("data called")
                    }
                }
            }
        }
        .resume()
    }
}

我的观点

import SwiftUI
import Combine

struct ContentView: View {
    @ObservedObject var data: NewsData
    var body: some View {
        List(data.news.articles , id: \.self) { article in
            Text(article.title ?? "")
        }
    }
}

标签: iosswiftswiftuicombine

解决方案


SwiftUI 中的数据绑定不会扩展到与服务器同步状态。如果这就是你想要的,那么你需要使用一些其他机制来告诉客户端服务器中有新数据(即 GraphQL、Firebase、发送推送、使用 Web 套接字或轮询服务器)。

一个简单的轮询解决方案看起来像这样,并注意您不应该在 init 中执行网络请求,只有当您从视图中出现 on 时,因为即使您看不到它们,SwiftUI 也会急切地对其视图进行水合。同样,您需要在屏幕外取消轮询:

struct Article: Codable, Identifiable {
  var id: String
}

final class ViewModel: ObservableObject {
  @Published private(set) var articles: [Article] = []
  private let refreshSubject = PassthroughSubject<Void, Never>()
  private var timerSubscription: AnyCancellable?
  init() {
    refreshSubject
      .map {
        URLSession
          .shared
          .dataTaskPublisher(for: URL(string: "someURL")!)
          .map(\.data)
          .decode(type: [Article].self, decoder: JSONDecoder())
          .replaceError(with: [])
      }
      .switchToLatest()
      .receive(on: DispatchQueue.main)
      .assign(to: &$articles)
  }
  func refresh() {
    refreshSubject.send()
    guard timerSubscription == nil else { return }
    timerSubscription = Timer
      .publish(every: 5, on: .main, in: .common)
      .autoconnect()
      .sink(receiveValue: { [refreshSubject] _ in
        refreshSubject.send()
      })
  }
  func onDisappear() {
    timerSubscription = nil
  }
}

struct MyView: View {
  @StateObject private var viewModel = ViewModel()
  var body: some View {
    List(viewModel.articles) { article in
      Text(article.id)
    }
    .onAppear { viewModel.refresh() }
    .onDisappear { viewModel.onDisappear() }
  }
}

推荐阅读