【问题标题】:Build and update lists automatically with SwiftUI and Combine使用 SwiftUI 和 Combine 自动构建和更新列表
【发布时间】:2021-01-04 23:58:11
【问题描述】:

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

我使用与 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 ?? "")
        }
    }
}

【问题讨论】:

  • 你说的自动更新列表是什么意思?它对我有用(我只将@ObservedObject var data: NewsData 更改为@ObservedObject var data = NewsData())。
  • @pawello2222 如果我将另一个项目添加到我的 json 数据中,我的视图中的列表会反映这些更改吗?我不想运行计时器来调用更新,这就是为什么我假设如果我的列表中的数据看到来自@Published var 的任何更改,它就会被更新。我是我的应用程序,我使用的是相对函数,它没有更新,也没有加载任何新文章而不杀死应用程序。
  • 不,如果您向 JSON 添加其他项目,您的应用将不会再次自动调用 URLSession.shared.dataTask@Published 仅表示如果此特定属性发生更改,将刷新视图。
  • @pawello2222 那么这篇文章真的不起作用吗?我只是很难弄清楚当数据发生变化时如何正确处理数据变化。我已经被这个问题困扰了好几天了,无法解决。计时器似乎是个坏主意,拉动刷新对我来说似乎很奇怪,刷新按钮也不是我想要的。
  • 我认为他们在文章中的意思是 List 最初是空的,使用 Combine 您可以使用从您的 URL 获取的数据自动填充它。但不会自动获取这些数据。

标签: ios swift swiftui combine


【解决方案1】:

我发现了一个漂亮的 swift 包,它可以让我轻松地重复网络调用。它被称为快速请求。感谢@pawello2222 帮助我解决了我的困境。

import Request

class NewsData: ObservableObject {
    @Published var news: News = News(articles: [])
    
    init() {
        test()
    }
    
    func test() {
        AnyRequest<News> {
            Url("https://raw.githubusercontent.com/ca13ra1/data/main/data.json")
        }
        .onObject { data in
            DispatchQueue.main.async() {
                self.news = data
            }
        }
        .update(every: 300)
        .update(publisher: Timer.publish(every: 300, on: .main, in: .common).autoconnect())
        .call()
    }
}

它现在按预期工作,可能是更简单的选择。

演示:

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 感谢您提供了一个很好的例子,并告诉我一些我不知道的事情,非常感谢您的评论。
    猜你喜欢
    • 2021-01-17
    • 1970-01-01
    • 2021-01-16
    • 2021-06-16
    • 2020-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-06
    相关资源
    最近更新 更多