Having Trouble with decoding Nested JSON Response in API call with SwiftUI - json

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

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

Related

Present results with alamofire/Swift

When I try to present the results I received this message "Response could not be decoded because of error:
The data couldn’t be read because it isn’t in the correct format."
This is my format and its right I think.
import Foundation
// MARK: - Response
struct Response: Codable {
let code: Int
let meta: Meta
let data: [Datum]
}
// MARK: - Datum
struct Datum: Codable {
let id, userID: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case title, body
}
}
// MARK: - Meta
struct Meta: Codable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Codable {
let total, pages, page, limit: Int
}
also I try with this code to view the result.
private func fetchData() {
self.task = AF.request(self.baseUrl, method: .get, parameters: nil)
.publishDecodable(type: [Response].self)
.sink(receiveCompletion: {(completion) in
switch completion {
case .finished:
()
case .failure(let error):
print(String(describing: error))
//print(error.localizedDescription)
}
}, receiveValue: {[weak self ](response) in
switch response.result {
case .success(let model): self?.presenters = model.map {PostPresenter(with: $0)}
case.failure(let error):
print(String(describing: error))
// print(error.localizedDescription)
}
})
}
And my post presenter code is this
struct PostPresenter: Identifiable {
let id = UUID()
let title: String
init(with model:Response) {
self.title = model.data
}
}
Two mistakes
The root object is a dictionary so it's (type: Response.self)
and model.data is [Datum] so declare
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response) {
self.data = response.data
}
}
And in a Codable context print the error always print(error), not print(String(describing: error)) and never print(error.localizedDescription)
My right Post presenter
struct PostPresenter: Identifiable {
let id = UUID()
let data: [Datum]
init(with response: Response<[Datum]>) {
self.data = response.data
}
init() {
data = []
}
}
And my finally format as I right.
struct Response<T: Decodable>: Decodable {
let code: Int
let meta: Meta
let data: T
}
// MARK: - Datum
struct Datum: Decodable {
let id, userID: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case title, body
}
}
// MARK: - Meta
struct Meta: Decodable {
let pagination: Pagination
}
// MARK: - Pagination
struct Pagination: Decodable {
let total, pages, page, limit: Int
}
And finally my homeview and how I present
struct HomeView: View {
#ObservedObject var viewModel = HomeAPIViewModel()
var body: some View {
ZStack {
Color(.white)
// Present the API Here
List(self.viewModel.presenter.data, id: \.id) {
Text($0.title)
}
.padding()
}
}
}

How to store data from json and from CoreData? SwiftUI

I study SwiftUI and I have an interesting task.
I need to get data from a site and store it in CoreData. Then, I need to get them from CoreData and show it in my View if I don't have internet.
I created my data model:
struct User: Codable, Identifiable, Hashable {
struct Friend: Codable, Identifiable, Hashable {
let id: String
let name: String
}
let id: String
let name: String
let isActive: Bool
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: Date
let friends: [Friend]
}
This is how I get the data form the site:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
//#FetchRequest(entity: CDUser.entity(), sortDescriptors: []) var users: FetchedResults<CDUser>
#State private var users = [User]()
// some code
// .............
func loadData() {
guard let url = URL(string: "https://****************.json") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let decoded = try decoder.decode([User].self, from: data)
DispatchQueue.main.async {
self.users = decoded
return
}
} catch {
print("Decode error:", error)
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
So now I don't know how to store a variable which will contains data from the site and from CoreData when it needed.
Can anyone give some advice?
User entity
Friend entity

Decode JSON using Codable and then populate my SwiftUI

I am new to Swift and SwiftUI. I am currently teaching myself how to code (loving it!) through hackingwithswift.com I am currently on Day 60 and I am stuck and not sure what to do from here.
The challenge is to decode some information using Codable and populate SwiftUI.
I created a struct to match the JSON, but when I go to run the app, I keep getting my error "Fetch Failed: Unknown Error" and therefore my UI won't update.
Would someone glance at my code and provide any pointers on where I am going wrong and possibly why? Thank you so much for any suggestions and help, it is much appreciated! Code is posted below.
Cody
import SwiftUI
struct Response: Codable {
var results: [User]
}
struct User: Codable, Identifiable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: String
let tags: [String]
struct FriendRole: Codable {
let id: String
let name: String
}
let friend: [FriendRole]
}
struct ContentView: View {
#State private var results = [User]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.address)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.results = decodedResponse.results
}
return
}
}
print("Fetch Failed: \(error?.localizedDescription ?? "Unkown Error").")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm also new to swiftUI this is how I managed to make it work by using Observable Objects
Here's the structures Since in the json file there is a [ in the very beginning you do not have to create a struct that has a User array you could create a struct and then create a variable of that struct type as an array
here's how I did it
I created separate files for instance for the structures i had a different file for them
here's what I have for my structure file
import Foundation
struct User : Codable, Identifiable {
let id : String
let isActive : Bool
let name : String
let age : Int
let company : String
let email : String
let address : String
let about : String
let registered : String
let tags = [String]()
let friends = [Friends]()
}
struct Friends : Codable {
let id : String
let name : String
}
I created another file for the observable object class
class JsonChannel : ObservableObject {
#Published var retVal = [User]()
func getInfo () {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {return}
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let data = data {
DispatchQueue.main.async {
do {
self.retVal = try JSONDecoder().decode([User].self, from: data)
}
catch {
print(error)
}
}
}
}.resume()
}
}
and here's what i have for my contentView file
import SwiftUI
struct ContentView : View {
#ObservedObject var info = JsonChannel()
var body: some View {
VStack {
Button(action: {
self.info.getInfo()
}) {
Text("click here to get info")
}
List {
ForEach (self.info.retVal) { item in
VStack {
Text("\(item.name)")
Text("\(item.address)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

I am getting nil while parsing JSON with Swift

I am learning Swift and I am studying for parsing JSON. I have a little complicated API for me and I can't get data from it. Can you let me know what mistakes I do below?
import UIKit
struct DataSource: Codable {
let status: Int
let totalResults: String
let articles: [Articles]
}
struct Articles: Codable {
let source: [Source]
let author: String
let title: String
}
struct Source: Codable {
let name: String
}
let source = URL(string: "http://newsapi.org/v2/everything?q=bitcoin&from=2020-03-29&sortBy=publishedAt&apiKey=51480e6fd4294010d")!
URLSession.shared.dataTask(with: source) { (data, response, error ) in
if let data = data {
let publishing = try? JSONDecoder().decode([DataSource].self, from: data)
print(publishing)
}
}.resume()
I fixed! Thank u for your all recommandations. Appreciated!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
struct DataSource: Codable {
let status: String?
let totalResults: Int?
let articles: [Articles]
}
struct Articles: Codable {
let source: Source
let author: String
let title: String
}
struct Source: Codable {
let id: String?
let name: String
}
let source = URL(string: "http://newsapi.org/v2/everything?q=bitcoin&from=2020-03-30&sortBy=publishedAt&apiKey=XXXXXX")!
URLSession.shared.dataTask(with: source) { (data, response, error ) in
do
{
let publishing = try JSONDecoder().decode(DataSource.self, from: data!)
print(publishing)
}
catch
{
print(error)
}
} .resume()
}
}
I make this example on playground
import UIKit
import PlaygroundSupport
// MARK: - ProfileResponse
struct ArticlesResponse: Codable {
var status: String
var totalResults: Int
var articles: [Article]
}
// MARK: - ProfileArticle
struct Article: Codable {
var source: Source
var author: String?
var title, articleDescription: String
var url: String
var urlToImage: String?
var publishedAt: String
var content: String?
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
// MARK: - ProfileSource
struct Source: Codable {
var id: String?
var name: String
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
if let url = URL(string: "http://newsapi.org/v2/everything?q=bitcoin&from=2020-03-30&sortBy=publishedAt&apiKey=f0b7d2e59beb4bcdb7a737a25c3542bc") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let decoder = JSONDecoder()
let data = try decoder.decode(ArticlesResponse.self, from: data)
label.text = "\(data.status)"
} catch let error {
print(error)
}
}
}.resume()
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Why isn't my text being added to my label?

Revised post: So the code posted below is my stuct
struct AnimeJsonStuff: Decodable {
let data: [AnimeDataArray]
}
struct AnimeLinks: Codable {
var selfStr : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
}
}
struct AnimeAttributes: Codable {
var createdAt : String?
var slug : String?
private enum CodingKeys : String, CodingKey {
case createdAt = "createdAt"
case slug = "slug"
}
}
struct AnimeRelationships: Codable {
var links : AnimeRelationshipsLinks?
private enum CodingKeys : String, CodingKey {
case links = "links"
}
}
struct AnimeRelationshipsLinks: Codable {
var selfStr : String?
var related : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
case related = "related"
}
}
struct AnimeDataArray: Codable {
let id: String?
let type: String?
let links: AnimeLinks?
let attributes: AnimeAttributes?
let relationships: [String: AnimeRelationships]?
private enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case links = "links"
case attributes = "attributes"
case relationships = "relationships"
}
}
This code is my function for parsing data:
func jsonDecoding() {
let jsonUrlString = "https://kitsu.io/api/edge/anime"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let animeJsonStuff = try JSONDecoder().decode(AnimeJsonStuff.self, from: data)
for anime in animeJsonStuff.data {
// print(anime.id)
// print(anime.type)
// print(anime.links?.selfStr)
let animeName = anime.attributes?.slug
print(animeName)
DispatchQueue.main.async {
self.nameLabel.text = animeName
}
for (key, value) in anime.relationships! {
// print(key)
// print(value.links?.selfStr)
// print(value.links?.related)
}
}
} catch let jsonErr {
print("Error serializing json", jsonErr)
}
}.resume()
}
This is what the console prints out:
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
Optional("cowboy-bebop")
Optional("cowboy-bebop-tengoku-no-tobira")
Optional("trigun")
Optional("witch-hunter-robin")
Optional("beet-the-vandel-buster")
Optional("eyeshield-21")
Optional("honey-and-clover")
Optional("hungry-heart-wild-striker")
Optional("initial-d-fourth-stage")
Optional("monster")
It now displays the text but it only displays the last optional called monster and not all the other ones when I have three cells. It only displays monster in each cell.
It should be
1st cell: Cowboy-bebpop
2nd cell: cowboy-bebop-tengoku-no-tobira
3rd cell: trigun
and etc
I can't see where do you set post variable.
Where you put nambeLabel into Controller's view hierarchy?
And maybe you should set nameLabel.text in main thread:
DispatchQueue.main.async {
self.nameLabel.attributedText = attributedText
}