SwiftUI - How to append and display nested JSON Data - json

I am playing with the Instagram API and I encounter the following issue: I managed to decode and access the JSON data but when I try to access a certain value inside a SwiftUI View I encounter the following error: Thread 1: Fatal error: Index out of range. I am totally aware that this is triggered by the fact that the API load is asynchronous but I can not find a fix for this.
I would greatly appreciate your help
Below you can find the API response
Here you can find my model and JSON Decoder
struct InstaAPI: Codable {
var name: String
var period: String
var description: String
var values: [ValueResponse]
}
struct ValueResponse: Codable {
let value: String
}
struct Entry: Codable {
let data: [InstaAPI]
}
class getData: ObservableObject {
#Published var response = [Entry]()
init() {
downloadJSON(from: URL(string: "https://graph.facebook.com/v6.0/17841402116620153/insights?metric=impressions&period=day&access_token=accounttoken")!)
}
func downloadJSON(from url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJson = try jsonDecoder.decode(Entry.self, from: data)
DispatchQueue.main.async {
self.response.append(parsedJson)
}
for data in parsedJson.data {
print(data.values[0].value)
}
} catch {
print(error)
}
}
}.resume()
}
}

maybe the old fashioned if would do the trick:
struct ContentView: View {
#ObservedObject var response = getData()
#State var responseNdx = 0
#State var dataNdx = 0
var body: some View {
VStack {
if responseNdx < self.response.response.count {
if dataNdx < self.response.response[responseNdx].data.count {
Text(self.response.response[responseNdx].data[dataNdx].name)
}
}
}
}
}

Related

How to read and display a dictionary from JSON?

I am working on an app that fetches the data from JSON and displays it.
However, I am stuck with an error saying Instance method 'appendInterpolation(_:formatter:)' requires that '[String : Int]' inherit from 'NSObject'
Here is my data structure:
struct Data: Codable {
var message: String
var data: Objects
}
struct Objects: Codable {
var date: String
var day: Int
var resource: String
var stats, increase: [String: Int]
}
Function to fetch the data:
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(Data.self, from: data)
self.data = decodedData
} catch {
print("Hey there's an error: \(error.localizedDescription)")
}
}
}.resume()
}
And a ContentView with the #State property to pass the placeholder data:
struct ContentView: View {
#State var data = Data(message: "", data: Objects(date: "123", day: 123, resource: "", stats: ["123" : 1], increase: ["123" : 1]))
var body: some View {
VStack {
Button("refresh") { getData() }
Text("\(data.data.date)")
Text("\(data.data.day)")
Text(data.message)
Text("\(data.data.stats)") //error is here
Here is an example of JSON response
I wonder if the problem is in data structure, because both
Text("\(data.data.date)")
Text("\(data.data.day)")
are working just fine. If there are any workarounds with this issue – please, I would highly appreciate your help!:)
stats is [String: Int], and so when you want to use it, you need to supply the key to get the value Int, the result is an optional that you must unwrap or supply a default value in Text
So use this:
Text("\(data.data.stats["123"] ?? 0)")
And as mentioned in the comments, do not use Data for your struct name.
EDIT-1: there are two ways you can make the struct fields camelCase; one is using the CodingKeys as shown in ItemModel, or at the decoding stage, as shown in the getData() function. Note, I've also updated your models to make them easier to use.
struct DataModel: Codable {
var message: String
var data: ObjectModel
}
struct ObjectModel: Codable {
var date: String
var day: Int
var resource: String
var stats: ItemModel
var increase: ItemModel
}
struct ItemModel: Codable {
var personnelUnits: Int
var tanks: Int
var armouredFightingVehicles: Int
// ...
// manual CodingKeys
// enum CodingKeys: String, CodingKey {
// case tanks
// case personnelUnits = "personnel_units"
// case armouredFightingVehicles = "armoured_fighting_vehicles"
// }
}
struct ContentView: View {
#State var dataModel = DataModel(message: "", data: ObjectModel(date: "123", day: 123, resource: "", stats: ItemModel(personnelUnits: 123, tanks: 456, armouredFightingVehicles: 789), increase: ItemModel(personnelUnits: 3, tanks: 4, armouredFightingVehicles: 5)))
var body: some View {
VStack {
Button("get data from Server") { getData() }
Text("\(dataModel.data.date)")
Text("\(dataModel.data.day)")
Text(dataModel.message)
Text("\(dataModel.data.stats.armouredFightingVehicles)") // <-- here
}
}
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // <-- here
dataModel = try decoder.decode(DataModel.self, from: data)
} catch {
print("--> error: \(error)")
}
}
}.resume()
}
}
}

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

Data immediately deallocated

I am currently working on a Headline UI Element for my app and I am having an issue with my data being immediately deallocated. I have been trying to get the console to print any data it is receiving, but I am not even seen that in the console. What I am trying to achieve is for the app to connect to its targeted "CDN" and then pull data for the headlines. Using PHP I am preforming a SQL Query to generate an array that the app will then feed off of. When running said scrip the following array is generated.
{"id":"1","title":"Meet ergoWare","header_image":"https://gba.ergoware.io/cache/content/topstory/ergo_news_01.svg","summary":"GBA's New Ergonomic Portal!"}
Stunning array yeah, so the next part is it will then be read and compiled in the app to create headline cards, but the issue is I cannot get the data to to load. I know there is something I am missing, but without the compiler pointing me in the direction I need, I'm fumbling around with it. So here is the Swift code.
import SwiftUI
struct Response: Codable {
var results: [Article]
}
struct Article: Codable, Identifiable {
var id: Int
var title: String
var header_image: String
var summary: String
}
struct HeadlineUI: View {
#State var results = [Article]()
var CDNLink: String
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(results) { result in
CardView(image: result.header_image, summary: result.summary, heading: result.title)
}
.onAppear(perform: { self.loadData() })
}
}
}
func loadData() {
guard let url = URL(string: CDNLink) 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
print(self.results)
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct HeadlineUI_Previews: PreviewProvider {
static var previews: some View {
HeadlineUI(CDNLink: "https://gba.ergoware.io/cdn?funct=fetchNews")
}
}
So what should happen is that it connects to the "CDN" and reads the array. That information then is plugged in and for each index of the results, defined by the id, a card should display with the top story image, article header and a short summary. I am starting to get a little more comfortable with Swift, but little hiccups like this keeps breaking my confidence down haha. Thanks for any teachable moments you can provide.
I found it, follow these steps:
According to your class Response, your service should return this JSON:
{
"results" : [{
"id":1,
"title":"Meet ergoWare",
"header_image":"https://gba.ergoware.io/cache/content/topstory/ergo_news_01.svg",
"summary":"GBA's New Ergonomic Portal!"
}]
}
Give it a try using this url
You should place .onAppear in your HStack not in your ForEach. Here is a the full example:
struct Response: Codable {
var results: [Article]
}
struct Article: Codable, Identifiable {
var id: Int
var title: String
var header_image: String
var summary: String
}
struct HeadlineUI: View {
#State var results = [Article]()
var CDNLink: String
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 20) {
Text("Response:")
ForEach(results) { result in
Text(result.title)
}
}.onAppear(perform: { self.loadData() })
}
}
func loadData() {
guard let url = URL(string: CDNLink) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
return
} else {
do {
let decodedResponse = try JSONDecoder().decode(Response.self, from: data!)
print(decodedResponse)
self.results = decodedResponse.results
} catch let err {
print("Error parsing: \(err)")
}
}
}.resume()
}
}

SwiftUI: Display JSON Data with Text() instead of List

I have got a problem with displaying JSON data on a SwiftUI View.
I tried several tutorials and read articles which are related to my problem, but nothing seems appropriate enough.
For example everyone displays JSON data from an API with a fancy list or pictures, but I only want to know how you can simply display one word on a view (without List{}).
I chose the PokeAPI to figure out how to display "Hi Charmander" with the Text() instruction.
Example of a list (and without ObservableObject and #Published)
I want to get rid of the List and use sth. Text(resultsVar[0].name).onAppear(perform: loadData) like instead
import SwiftUI
struct pokeRequest:Codable {
var results: [Result]
}
struct Result:Codable {
var name:String
}
struct ViewOne: View {
#State var resultsVar = [Result]()
var body: some View {
VStack{
//unfortunately this does not work:
//Text(resultsVar[0].name).onAppear(perform: loadData)
List(resultsVar, id: \.name) { item in
VStack(alignment: .leading) {
Text("Hi \(item.name)")
}
}
.onAppear(perform: loadData)
}
}
func loadData(){
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3") 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(pokeRequest.self, from: data) {
DispatchQueue.main.async {
self.resultsVar = decodedResponse.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ViewOne_Previews: PreviewProvider {
static var previews: some View {
ViewOne()
}
}
Second try with a different approach (without .onAppear())
In this approach I tried with class: Observable Object and #Published but I also didn't come to my wished UI-output.
import SwiftUI
struct pokeRequest2:Codable {
var results2: [pokeEach2]
}
struct pokeEach2:Codable {
var name2:String
}
class Webservice:ObservableObject {
#Published var pokeInfo: [pokeRequest2] = [pokeRequest2]()
func decodepokemon() {
let session = URLSession.shared
let url = URL(string: "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3")!
let task = session.dataTask(with: url) { data, response, error in
if error != nil || data == nil {
print("Client error!")
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
print("Server error!")
return
}
guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
do {
let response = try JSONDecoder().decode(pokeRequest2.self, from: data!)
print(self.pokeInfo[0].results2[0].name2)
DispatchQueue.main.async {
self.pokeInfo = [response]
}
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
init() {
decodepokemon()
}
}
struct ViewTwo: View {
#ObservedObject var webservice: Webservice = Webservice()
var body: some View {
Text("please help")
//Does also not work: Text(self.webservice.pokeInfo2[0].results2[0].name2)//.onAppear()
//Since a few minutes somehow the debug area prints "JSON error: The data couldn’t be read because it is missing." instead of "charmander"
}
}
struct ViewTwo_Previews: PreviewProvider {
static var previews: some View {
ViewTwo()
}
}
I tried several tutorials and read articles which are related to my problem, but nothing seems appropriate enough.
I would highly appreciate any help :-)
Thanks in advance!
I may be misunderstanding the question but in SwiftUI text can be displayed as follows:
Text("Hi" + item.name)
But as I say I'm not sure if that's the question.
As you will dynamically change the list items, you have to use .id() modifier. And in order to use .id(), the result must conforms to Hashable. The following code can help you solve the problem.
struct Result:Codable, Hashable {
var name:String
}
struct ViewOne: View {
#State var resultsVar = [Result]() // [Result(name: "firsy"),Result(name: "second"), ]//[Result]()
var body: some View {
VStack{
Spacer()
//unfortunately this does not work:
//Text(resultsVar[0].name).onAppear(perform: loadData)
List(resultsVar, id: \.name) { item in
VStack(alignment: .leading) {
Text("Hi \(item.name)")
}
}.id(resultsVar)
}.onAppear(perform: loadData)
I am by no means an expert (started using Swift less than three days ago) but I think I might have what you are looking for. This enables you to call a simple REST API that contains only a simple dictionary (in my case the dictionary contains "Differential": 5.22) and then display that number as text when a button is pressed. Hope the following is helpful!
struct DifferentialData: Decodable {
var Differential: Double
}
struct ContentView: View {
#State var data = DifferentialData(Differential: 0.00)
func getData() {
guard let url = URL(string: "Enter your URL here")
else { return } //Invalid URL
URLSession.shared.dataTask(with: url) { data, response, error in
let differentials = try! JSONDecoder().decode(DifferentialData.self, from: data!)
print(differentials)
DispatchQueue.main.async {
self.data = differentials
}
}
.resume()
}
var body: some View {
VStack{
Text("\(data.Differential)")
Button("Refresh data") {self.getData()}
}
}
}

How do you display JSON Data in swiftUI

I have problem with showing JSON Data in SwiftUI, I get the data from Genius API I currently search for song and can confirm that I get the data extracted correctly; example I can print out the title of the result:
This is how I fetch the data
class NetworkManager: ObservableObject {
var objectWillChange = PassthroughSubject<NetworkManager, Never>()
var fetchedSongsResults = [hits]() {
willSet {
objectWillChange.send(self)
}
}
init() {
fetchSongs()
}
func fetchSongs() {
guard let url = URL(string: "https://api.genius.com/search?q=Sia") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.setValue("Bearer TOKEN", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: urlRequest) {data, response, error in
guard let data = data else { return }
//print(String(decoding: data, as: UTF8.self))
let songs = try! JSONDecoder().decode(feed.self, from: data)
DispatchQueue.main.async {
self.fetchedSongsResults = songs.response.hits
}
}.resume()
}
}
So when I get the data I save to the variable fetchedSongsResults and this seems correctly but for what ever reason when I try to print the count for example it says that i empty and also I can't loop through the fetchedSongsResults using a list or ForEach this is how, (which I believe s because I have not made the model identifiable) I tried to print the count of fetchedSongsResults,
This initialized outside the body (just so you know)
#State var networkManager = NetworkManager()
This is inside the body
Text("\(networkManager.fetchedSongsResults.count)")
If your are wondering how my structure looks like when I decode the JSON Data then here it is
struct feed: Codable {
var meta: meta
var response: response
}
struct meta: Codable {
var status: Int
}
struct response: Codable {
var hits: [hits]
}
struct hits: Codable {
var index: String
var type: String
var result: song
}
struct song: Codable, Identifiable {
var id: Int
var header_image_thumbnail_url: String
var url: String
var title: String
var lyrics_state: String
var primary_artist: artist
}
struct artist: Codable {
var name: String
}
Try: #ObservedObject var networkManager = NetworkManager().