Data immediately deallocated - json

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

Related

Fetch data from nested JSON API in SwiftUI (object in array in object in another object)

Beginner here, in a bit over my head with this. ;)
I've found examples that have shown me how to get data from a JSON API feed if the feed is structured as an array of objects, but I don't know how to approach getting the data (specifically, url and title) if the data I'm retrieving comes back in a more complex nested structure like this one:
{
"races": {
"videos": [{
"id": 1,
"url": "firsturl",
"title": "1st Video Title"
}, {
"id": 2,
"url": "secondurl",
"title": "2nd Video Title"
}]
}
}
I've succeeded at get data from another API feed that's structured as a simple array of objects--it's like what's above but without the extra two lead-in objects, namely this: { "races": { "videos":
Here's the code I pieced together from a few examples that worked for the simple array:
import SwiftUI
struct Video: Codable, Identifiable {
public var id: Int
public var url: String
public var title: String
}
class Videos: ObservableObject {
#Published var videos = [Video]()
init() {
let url = URL(string: "https://exampledomain.com/jsonapi")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let videoData = data {
let decodedData = try JSONDecoder().decode([Video].self, from: videoData)
DispatchQueue.main.async {
self.videos = decodedData
}
} else {
print("no data found")
}
} catch {
print("an error occurred")
}
}.resume()
}
}
struct VideosView: View {
#ObservedObject var fetch = Videos()
var body: some View {
VStack {
List(fetch.videos) { video in
VStack(alignment: .leading) {
Text(video.title)
Text("\(video.url)")
}
}
}
}
}
I've spent several hours over a few days reading and watching tutorials, but so far nothing is sinking in to help me tackle the more complex JSON API feed. Any tips would be greatly appreciated!
UPDATE:
With the help of a Swift Playground tutorial and the suggested structs mentioned in the comments below, I've succeeded at retrieving the more complex data, but only in Swift Playgrounds, using this:
import SwiftUI
struct Welcome: Codable {
let races: Races
}
struct Races: Codable {
let videos: [Video]
}
struct Video: Codable {
let id: Int
let url, title: String
}
func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
getJSON(urlString: "https://not-the-real-domain.123/api/") { (followers:Welcome?) in
if let followers = followers {
for result in followers.races.videos {
print(result.title )
}
}
}
Now, I'm struggling with how to properly integrate this Playgrounds snippet in to the working SwiftUI file's VideosViews, etc.
UPDATE 2:
import SwiftUI
struct Welcome: Codable {
let races: RaceItem
}
struct RaceItem: Codable {
let videos: [VideoItem]
}
struct VideoItem: Codable {
let id: Int
let url: String
let title: String
}
class Fetcher: ObservableObject {
func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
}
struct JSONRacesView: View {
#ObservedObject var fetch = Fetcher()
getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
if let followers = followers {
for result in followers.races.videos {
print(result.title )
}
}
}
var body: some View {
VStack {
List(fetch.tracks) { track in
VStack(alignment: .leading) {
Text(track.title)
Text("\(track.url)")
}
}
}
}
There's a great site called QuickType (app.quicktype.io) where you can paste in some JSON and get the Swift structs generated for you. Here's what it gives you:
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let races: Races
}
// MARK: - Races
struct Races: Codable {
let videos: [Video]
}
// MARK: - Video
struct Video: Codable {
let id: Int
let url, title: String
}
They have a bug in their template generator that mangles the demo line (I've submitted a pull request that is merged but isn't live on the site at the time of this writing), but here's what it should look like:
let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
Using do/try so you can catch the errors, you can decode the data and reach the lower levels by doing:
do {
let welcome = try JSONDecoder().decode(Welcome.self, from: jsonData)
let videos = welcome.races.videos //<-- this ends up being your [Video] array
} catch {
//handle any errors
}
Update, based on your comments and updates:
You chose to go a little bit of a different route than my initial suggestion, but that's fine. The only thing that I would suggest is that you might want to deal with handling errors at some point rather than just returning nil in all of the completions (assuming you need to handle errors -- maybe it just not loading is acceptable).
Here's a light refactor of your code:
class Fetcher: ObservableObject {
#Published var tracks : [VideoItem] = []
private func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
func fetchData() {
getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
DispatchQueue.main.async {
self.tracks = followers?.races.videos ?? []
}
}
}
}
struct JSONRacesView: View {
#StateObject var fetch = Fetcher()
var body: some View {
VStack {
List(fetch.tracks, id: \.id) { track in
VStack(alignment: .leading) {
Text(track.title)
Text("\(track.url)")
}
}
}.onAppear {
fetch.fetchData()
}
}
}
You can see that now Fetcher has a #Published property that will store the tracks ([VideoItem]). getJSON is still in fetcher, but now it's private just to show that it isn't meant to be called directly. But, now there's a new function called fetchData() that your view will call. When fetchData gets data back, it sets the #Published property to that data. I used the ?? operator to tell the compiler that if followers is nil, then just use [] instead. This is all in a DispatchQueue.main.async block because the URL call is probably not going to return on the main thread and we need to make sure to always update the UI on the main thread (Xcode will warn you about this at runtime if you update the UI on a different thread).
JSONRacesView calls fetchData in onAppear, which happens exactly when it sounds like it will.
Last thing to note is I used #StateObject instead of #ObservedObject. If you're not on iOS 14 or macOS 11 yet, you could use #ObservedObject instead. There are some differences outside the scope of this answer, but that are easily Google-able.

How can I read json from url, output to app screen? SwiftUI

I`m new at swift and need to read json file from url. I managed to get the data and output it to the console, but how can I get it to the application screen?
import SwiftUI
struct User: Codable {
let Login: String
let Password: String
}
struct AllTEC: View {
var body: some View {
Text("asd").onAppear(perform: loadD)
}
}
extension AllTEC
{
func loadD() {
let url = URL(string: "https://symbiosys-a415a.firebaseio.com/USER.json")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
print("error: \(error!)")
return
}
guard let data = data else{
print("no data")
return
}
guard let user = try? JSONDecoder().decode(User.self, from: data) else {
print("Error: Couldn't decode data ito user")
return
}
print("gotten user is \(user.Login)")
}
task.resume()
}
}
struct AllTEC_Previews: PreviewProvider {
static var previews: some View {
AllTEC()
}
}
The json structure is pretty simple.
{
"Login":"Kapitan",
"Password":"M3030"
}
Simply make a #State variable user for storing the returned User in AllTEC.
struct AllTEC: View {
#State var user: User?
var body: some View {
Text("Login: \(user?.Login ?? "")")
.onAppear(perform: loadD)
}
}
And set user right after you decode it in the network call:
print("gotten user is \(user.Login)")
DispatchQueue.main.async {
self.user = user
}

Decoding Exchange Rate JSON in SwiftUI

I am trying to decode https://api.exchangeratesapi.io/latest, provided by Exchange Rates API. I'm applying several tutorials I found online, but when I apply my own details, I get an error. My code looks as following:
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
let base: String
let date: String
let rates: [String:Double]
}
The function to retrieve the data:
func loadData() {
guard let url = URL(string: "https://api.exchangeratesapi.io/latest") 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) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse.results
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
And my view:
import SwiftUI
struct ExchangeRateTest: View {
#State private var results = [Result]()
var body: some View {
List(results, id: \.base) { item in
VStack(alignment: .leading) {
Text(item.base)
}
}.onAppear(perform: loadData)
}
}
The error I get is: Fetch Failed: Unknown Error, suggesting that the app is not able to read the online data. What can cause this?
It has nothing to do with my network connection; if I apply another JSON this approach works fine.
Any help would greatly be appreciated.
you can read like this:
struct RateResult: Codable {
let rates: [String: Double]
let base, date: String
}
struct ContentView: View {
#State private var results = RateResult(rates: [:], base: "", date: "")
func loadData() {
guard let url = URL(string: "https://api.exchangeratesapi.io/latest") 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(RateResult.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}

SwiftUI - How to append and display nested JSON Data

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

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