I'm currently trying to utilize the NewsApi api to allow me to retrieve certain articles. I currently do have a model for which I generated
import Foundation
// MARK: - Welcome
struct NewsResponse: Codable {
let status: String
let totalResults: Int
let articles: [Article]
enum CodingKeys: String, CodingKey {
case status = "status"
case totalResults = "totalResults"
case articles = "articles"
}
}
// MARK: - Article
struct Article: Codable, Identifiable {
let id = UUID()
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 = "source"
case author = "author"
case title = "title"
case articleDescription = "description"
case url = "url"
case urlToImage = "urlToImage"
case publishedAt = "publishedAt"
case content = "content"
}
}
// MARK: - Source
struct Source: Codable {
let id: String?
let name: String
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
I also do have my Webservice that decodes the API link which is at let pokeNews = URL(string:"https://newsapi.org/v2/everything?q=pokemon&apiKey=*APIKey*")!
The webservice code is here and I had used a Mohammed Azam tutorial for that: https://www.youtube.com/watch?v=YOCZuZz4vAw
import Foundation
class NewsWebService: ObservableObject{
@Published var news: [Article] = []
func getNews() async throws{
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{@MainActor in
self.news = try JSONDecoder().decode([Article].self, from: data)
}
}
}
Now the main issue I'm having is that I'm at a loss of understanding at how I would access the articles array from the NewsResponse model and retrieve the article.title, etc. I've attempted it multiple times, but am not quite sure how to proceed. I've looked at other tutorials, but I feel that I'm still not gaining a sense of understanding at how to effectively call apis in swift. The code in which I'm attempting to call the API within is here:
import SwiftUI
struct NewsListView: View {
@EnvironmentObject var newsWebService: NewsWebService
var body: some View {
List(newsWebService.news){ article in
Text(article.title)
}
HStack(alignment: .center, spacing: 16, content: {
//Article Image
Image("yak0")
.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.clipShape(RoundedRectangle(cornerRadius: 12))
//Article Title
VStack(alignment: .leading, spacing: 8){
Text("Massive News Surrounding Pokemon")
.font(.title2)
.fontWeight(.heavy)
.foregroundColor(.red)
//Article Source
Text("CNN").font(.footnote)
.fontWeight(.bold)
.multilineTextAlignment(.leading)
.foregroundColor(.yellow)
}
}).onTapGesture {
//go to webview
}
}
}
struct NewsListView_Previews: PreviewProvider {
static var previews: some View {
NewsListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(NewsWebService())
}
}
Any help would be appreciated. Thank you so much!
Edited for NewsResponse portion
import Foundation
class NewsWebService: ObservableObject{
@Published var news: NewsResponse?
func getNews() async throws{
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{@MainActor in
self.news = try JSONDecoder().decode(NewsResponse.self, from: data)
}
}
}
The Line showing the error: Initializer 'init(_:rowContent:)' requires that 'NewsResponse' conform to 'RandomAccessCollection'
List(newsWebService.news!){ article in
Text(article.title)
}
CodePudding user response:
try this example code (note the mods in Article
), works well for me. Note, do not show your secret api key, remove it:
struct ContentView: View {
@StateObject var newsWebService = NewsWebService()
var body: some View {
NewsListView().environmentObject(newsWebService)
}
}
struct NewsListView: View {
@EnvironmentObject var newsWebService: NewsWebService
var body: some View {
List(newsWebService.news?.articles ?? []){ article in // <-- here
Text(article.title)
}
.task {
do{
try await newsWebService.getNews()
} catch{
print("---> task error: \(error)")
}
}
}
}
class NewsWebService: ObservableObject{
@Published var news: NewsResponse?
func getNews() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{@MainActor in
self.news = try JSONDecoder().decode(NewsResponse.self, from: data)
}
}
}
struct NewsResponse: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable, Identifiable {
let id = UUID()
let source: Source
let author: String?
let title, articleDescription: String
let url: String
let urlToImage: String?
let publishedAt: String? // <-- here
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
}
This is assuming Constants.url.pokeNews
is the url for "https://newsapi.org/v2/everything?q=pokemon&apiKey=YOURKEY"
EDIT-1:
If you want to use Article
, let publishedAt: Date?
, instead of String?
, then use:
func getNews() async throws {
let (data, _) = try await URLSession.shared.data(from: Constants.url.pokeNews)
Task{@MainActor in
let decoder = JSONDecoder() // <-- here
decoder.dateDecodingStrategy = .iso8601 // <-- here
self.news = try decoder.decode(NewsResponse.self, from: data)
}
}