如何将数据绑定到 ViewModel 以便在 MVVM 中的 UI 上显示它?
How to bind data to ViewModel for showing it on UI in MVVM?
在我的应用程序中,我使用的是 MVVM 模式。
下面是我的模型。
struct NewsModel: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url: String
let urlToImage: String?
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
下面是我的 ViewModel。用于显示来自 API.
的数据
struct NewsArticleViewModel {
let article: Article
var title:String {
return self.article.title
}
var publication:String {
return self.article.articleDescription!
}
var imageURL:String {
return self.article.urlToImage!
}
}
下面是我的API请求class。
class Webservice {
func getTopNews(completion: @escaping (([NewsModel]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let news = try? JSONDecoder().decode([NewsModel].self, from: data)
DispatchQueue.main.async {
completion(news)
}
}.resume()
}
}
收到我 API 的回复后,我想在屏幕上显示它。为此,我在下面添加了 ViewModel。
class NewsListViewModel: ObservableObject {
@Published var news: [NewsArticleViewModel] = [NewsArticleViewModel]()
func load() {
fetchNews()
}
private func fetchNews() {
Webservice().getTopNews {
news in
if let news = news {
//How to bind this data to NewsArticleViewModel and show it on UI?
}
}
}
}
请告诉我。为了在 UI.
上展示,我必须写些什么
根据 newsapi.org
的文档,您的请求将 return 一个 NewsModel
对象而不是数组。因此,将 Webservice
class 更改为:
class Webservice {
//Change the completion handler to return an array of Article
func getTopNews(completion: @escaping (([Article]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// decode to a single NewsModel object instead of an array
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let news = try? decoder.decode(NewsModel.self, from: data)
DispatchQueue.main.async {
// completion with an optional array of Article
completion(news?.articles)
}
}.resume()
}
}
您需要将这些接收到的值映射到 NewsArticleViewModel
类型。例如:
Webservice().getTopNews { articles in
if let articles = articles {
self.news = articles.map{NewsArticleViewModel(article: [=11=])}
}
}
并从 NewsArticleViewModel
结构中删除 let news: NewsModel
,因为它不需要。
编辑:
好像是:
let publishedAt: Date
正在抛出错误。 Jsondecoder 无法将字符串解释为日期。改变你的 Webservice
。我在我的回答中更新了它。
您可以删除遗留的 MVVM 模式并在适当的 SwiftUI 中执行此操作,如下所示:
struct ContentView: View {
@State private var articles = [Article]()
var body: some View {
NavigationView {
List(articles) { article in
Text(article.title)
}
.navigationTitle("Articles")
}
.task {
do {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb")!
let (data, _) = try await URLSession.shared.data(from: url)
articles = try JSONDecoder().decode([Article].self, from: data)
} catch {
articles = []
}
}
}
}
在我的应用程序中,我使用的是 MVVM 模式。 下面是我的模型。
struct NewsModel: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url: String
let urlToImage: String?
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
下面是我的 ViewModel。用于显示来自 API.
的数据 struct NewsArticleViewModel {
let article: Article
var title:String {
return self.article.title
}
var publication:String {
return self.article.articleDescription!
}
var imageURL:String {
return self.article.urlToImage!
}
}
下面是我的API请求class。
class Webservice {
func getTopNews(completion: @escaping (([NewsModel]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let news = try? JSONDecoder().decode([NewsModel].self, from: data)
DispatchQueue.main.async {
completion(news)
}
}.resume()
}
}
收到我 API 的回复后,我想在屏幕上显示它。为此,我在下面添加了 ViewModel。
class NewsListViewModel: ObservableObject {
@Published var news: [NewsArticleViewModel] = [NewsArticleViewModel]()
func load() {
fetchNews()
}
private func fetchNews() {
Webservice().getTopNews {
news in
if let news = news {
//How to bind this data to NewsArticleViewModel and show it on UI?
}
}
}
}
请告诉我。为了在 UI.
上展示,我必须写些什么根据 newsapi.org
的文档,您的请求将 return 一个 NewsModel
对象而不是数组。因此,将 Webservice
class 更改为:
class Webservice {
//Change the completion handler to return an array of Article
func getTopNews(completion: @escaping (([Article]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// decode to a single NewsModel object instead of an array
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let news = try? decoder.decode(NewsModel.self, from: data)
DispatchQueue.main.async {
// completion with an optional array of Article
completion(news?.articles)
}
}.resume()
}
}
您需要将这些接收到的值映射到 NewsArticleViewModel
类型。例如:
Webservice().getTopNews { articles in
if let articles = articles {
self.news = articles.map{NewsArticleViewModel(article: [=11=])}
}
}
并从 NewsArticleViewModel
结构中删除 let news: NewsModel
,因为它不需要。
编辑:
好像是:
let publishedAt: Date
正在抛出错误。 Jsondecoder 无法将字符串解释为日期。改变你的 Webservice
。我在我的回答中更新了它。
您可以删除遗留的 MVVM 模式并在适当的 SwiftUI 中执行此操作,如下所示:
struct ContentView: View {
@State private var articles = [Article]()
var body: some View {
NavigationView {
List(articles) { article in
Text(article.title)
}
.navigationTitle("Articles")
}
.task {
do {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb")!
let (data, _) = try await URLSession.shared.data(from: url)
articles = try JSONDecoder().decode([Article].self, from: data)
} catch {
articles = []
}
}
}
}