SWIFT UI request - json

Im trying to make a request on Swift UI in a data file, but nothing is happening on and I seem to not be getting back any data. Im not sure where the issue is, keep in mind I just started swiftUI 2days ago. Any help would be greatly appreciated.
Data Im trying to retrieve.
[
{
"id": 1119,
"make": "Audi",
"model": "100",
"generation": "100 Avant (4A,C4)",
"engine_modification": "2.8 V6 E (174 Hp) quattro Automatic",
"year": 1991,
"powertrain_architecture": "Internal Combustion engine",
"body_type": "Station wagon (estate)",
"number_of_seats": 5,
"number_of_doors": 5,
"urban_fuel_consumption": 13.5,
"extra_urban_fuel_consumption": 8,
"combined_fuel_consumption": 10,
"fuel_type": "Petrol (Gasoline)",
"acceleration": 9.5,
"top_speed": 207,
"power": 174,
"torque": 250,
"engine_location": "Front, Longitudinal",
"engine_displacement": 2771,
"number_of_cylinders": 6,
"position_of_cylinders": "V-engine",
"number_of_valves_per_cylinder": 2,
"fuel_system": "Multi-point indirect injection",
"engine_aspiration": "Naturally aspirated engine",
"kerb_weight": 1550,
"fuel_tank_capacity": 80,
"drive_wheel": "All wheel drive (4x4)",
"number_of_gears": 4,
"front_brakes": "Ventilated discs",
"rear_brakes": "Disc"
},
{
"id": 1120,
"make": "Audi",
"model": "100",
"generation": "100 Avant (4A,C4)",
"engine_modification": "2.8 V6 E (174 Hp) quattro",
"year": 1991,
"powertrain_architecture": "Internal Combustion engine",
"body_type": "Station wagon (estate)",
"number_of_seats": 5,
"number_of_doors": 5,
"urban_fuel_consumption": 13.5,
"extra_urban_fuel_consumption": 8,
"combined_fuel_consumption": 10,
"fuel_type": "Petrol (Gasoline)",
"acceleration": null,
"top_speed": null,
"power": 174,
"torque": 250,
"engine_location": "Front, Longitudinal",
"engine_displacement": 2771,
"number_of_cylinders": 6,
"position_of_cylinders": "V-engine",
"number_of_valves_per_cylinder": 2,
"fuel_system": "Multi-point indirect injection",
"engine_aspiration": "Naturally aspirated engine",
"kerb_weight": 1550,
"fuel_tank_capacity": 80,
"drive_wheel": "All wheel drive (4x4)",
"number_of_gears": 5,
"front_brakes": "Ventilated discs",
"rear_brakes": "Disc"
},
Code for request
import SwiftUI
import Foundation
struct astonMartin: Decodable, Identifiable{
let id: Int
var make: String
var model: String
var generation: String
var engine_modification: String
var year: Int
var powertrain_architecture: String
var body_type: String
var number_of_seats: Int
var number_of_doors: Int
var urban_fuel_consumption: Int
var extra_urban_fuel_consumption: Int
var combined_fuel_consumption: Int
var fuel_type: String
var acceleration: Int
var top_speed: Int
var power: Int
var torque: Int
var engine_location: String
var engine_displacement: Int
var number_of_cylinders: Int
var position_of_cylinders: String
var number_of_valves_per_cylinder: Int
var fuel_system: String
var engine_aspiration: String
var kerb_weight: Int
var fuel_tank_capacity: Int
var drive_wheel: String
var number_of_gears: Int
var front_brakes: String
var rear_brakes: String
}
class secondaryCall: ObservableObject {
#Published var cars: [astonMartin] = []
func getCars() {
guard let url = URL(string: "URL INSERTED HERE") else { fatalError("Missing URL") }
let urlRequest = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("Request error: ", error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let decodedCars = try JSONDecoder().decode([astonMartin].self, from: data)
self.cars = decodedCars
} catch let error {
print("Error decoding: ", error)
}
}
}
}
dataTask.resume()
}
}
content view
import SwiftUI
struct ContentView: View {
#State var isLinkActive = false
#EnvironmentObject var allCars: secondaryCall
var body: some View {
NavigationView{
ZStack{
Image("car")
.resizable()
.scaledToFill()
.ignoresSafeArea(.all)
.overlay(
Rectangle()
.opacity(0.6)
.ignoresSafeArea(.all)
)
NavigationLink(destination: HomeView()) {
Text("Click to view all cars")
.foregroundColor(.white)
}
.padding()
.background(.orange)
.clipShape(Capsule()
)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(secondaryCall())
}
}
Home view:
//
// HomeView.swift
// project
//
//
import SwiftUI
struct HomeView: View {
#EnvironmentObject var allCars: secondaryCall
var body: some View {
ScrollView{
Text("All cars for Aston Martin")
.bold()
.textCase(.uppercase)
.font(.headline)
ForEach(allCars.cars){car in
HStack(alignment: .top){
Text("\(car.make)")
}
}
}
.onAppear{
allCars.getCars()
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
.environmentObject(secondaryCall())
}
}
Project App
//
// projectApp.swift
// project
//
//
import SwiftUI
#main
struct projectApp: App {
var allCars = secondaryCall()
var body: some Scene {
WindowGroup {
SplashView()
.environmentObject(allCars)
}
}
}

Form your data, I see that the reason you not getting data is that because you construct some variables in struct astonMartin with wrong type compare to the JSON you given.
Given you some example
In Json: "urban_fuel_consumption": 13.5, which should be Double but in struct astonMartin you set is Int
In Json: "acceleration": null, which should be Double? because it is nil here but in struct astonMartin you set is Double
In Json: "top_speed": null, which should be Int? because it is nil here but in struct astonMartin you set is Int
And maybe more of that ...
You should recheck each type of variables in your struct
Code will change like follow
struct astonMartin: Decodable, Identifiable {
let id: Int
var make: String
var model: String
var generation: String
var engine_modification: String
var year: Int
var powertrain_architecture: String
var body_type: String
var number_of_seats: Int
var number_of_doors: Int
var urban_fuel_consumption: Double // was Int
var extra_urban_fuel_consumption: Int
var combined_fuel_consumption: Int
var fuel_type: String
var acceleration: Double? // was Int
var top_speed: Int? // was Int
var power: Int
var torque: Int
var engine_location: String
var engine_displacement: Int
var number_of_cylinders: Int
var position_of_cylinders: String
var number_of_valves_per_cylinder: Int
var fuel_system: String
var engine_aspiration: String
var kerb_weight: Int
var fuel_tank_capacity: Int
var drive_wheel: String
var number_of_gears: Int
var front_brakes: String
var rear_brakes: String
}
More over, when you parse json not right it prints error in debug mode because you currently catch error there. Make sure you notice that.

Related

Swift: Decode flat JSON to an structured object

wonder if there is a simple way to get a simple flat json to a struct with a structure
json:
{
"date": "2022-02-24T00:00:00.000Z",
"personel": 800,
"plane": 7,
"drone": 6,
}
what I want is to get a structure like this:
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
I thought there should be some simple way to make it work
As specified in comments it would be better to create temporary DBObject and initialize your Day object with it.
Simple DBObject:
struct DBObject: Decodable {
let date: String?
let personel, plane, drone: Int?
}
Adding init() to your Day
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
init(from dbObject: DBObject) {
let dayData = DayData(personel: dbObject.personel ?? 0,
plane: dbObject.plane ?? 0,
drone: dbObject.drone ?? 0)
self.dayData = [dayData]
self.date = dbObject.date ?? ""
}
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
Decoding Day from Data response:
func returnDay(from dataResponse: Data) -> Day {
do {
let decoder = JSONDecoder()
let dbObject = try decoder.decode(DBObject.self,
from: dataResponse)
return Day(from: dbObject)
} catch {
fatalError("Cannot decode object")
}
}

SwiftUI parse a json from an URL with entry values

I'm stuck when a I need to load a json file to swiftUI when this json comes from an URL with entry variables that must be filled by the user before getting the data.
I have the following code:
// This is the structure to get the data from the API.
struct loka_result: Codable {
var date: String
var time: String
var unix_time: Int
var seqNumber : Int
var lat: Double
var lng: Double
var device: String
var accuracy: Double
var info: String
var temperature : Double?
var battery_voltage : Float?
}
// Now conform to Identifiable
extension loka_result: Identifiable {
public var id: Int { return unix_time }
}
// Model Data to get the data
final class ModelData: ObservableObject {
#Published var allthelokas : [loka_result] = loadAllData(deviceId:selected_loka, timeDelta: selected_delta)
}
// Function to get and parse the data
func loadAllData<T: Decodable>(deviceId:String,timeDelta:Int) -> T
{
let url = URL(string: "https://xxxx.php?device=\(deviceId)&hours=\(timeDelta)"),
data = try? Data(contentsOf: url!)
return
try! JSONDecoder().decode(T.self, from: data!)
}
Then in TempChartView I would like to get a list
import SwiftUI
struct TempChartNew: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
VStack {
List(modelData.allthelokas) { j in
// ForEach(modelData.allthelokas) { jj in
HStack {
Text(j.device)
Text(j.date)
Text(j.time)
Text(String(j.seqNumber))
}
}
}
}
}
struct TempChartNew_Previews: PreviewProvider {
static var previews: some View {
TempChartNew().environmentObject(ModelData())
}
}
all this its working because just to test it I have added two global variables to fill in the data for the function.
var selected_loka: String = "3JJJ30"
var selected_delta: Int = 24
But the ideia would be for the view to have two entry points.
And its here where I'm stuck ! I don't know how to pass the variables to the class.
Does anyone can please point me in the correct direction ? - Thank you

Parse a Json from a url with entry values and list them in SwiftUI

I'm stuck on this.
I have a json that I'm parsing and that Json has an entry value and I want to do a swift list based in user input that will trigger from the external json different responses.
I have the following code so far
import SwiftUI
import Combine
import Foundation
var lokas : String = "ABCY"
struct ContentView: View {
#State var name: String = ""
#ObservedObject var fetcher = Fetcher()
var body: some View {
VStack {
TextField("Enter Loka id", text: $name)
List(fetcher.allLokas) { vb in
VStack (alignment: .leading) {
Text(movie.device)
Text(String(vb.seqNumber))
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}
}
}
public class Fetcher: ObservableObject {
#Published var allLokas = [AllLoka_Data]()
init(){
load(devices:lokas)
}
func load(devices:String) {
let url = URL(string: "https://xxx.php?device=\(devices)&hours=6")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([AllLoka_Data].self, from: d)
DispatchQueue.main.async {
self.allLokas = decodedLists
}
}else {
print("No Data")
}
} catch {
print ("Error")
}
}.resume()
}
}
struct AllLoka_Data: Codable {
var date: String
var time: String
var unix_time: Int
var seqNumber : Int
}
// Now conform to Identifiable
extension AllLoka_Data: Identifiable {
var id: Int { return unix_time }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But I just added a Global variable to test it, but how do I pass the variable name to make the function work ?
Thank you

How to use custom data from JSON in ForEach SwiftUI

I have JSON file with custom data. Goal is to use this data in PostCard() in ForEach(){}. But evetytime i try to use user inside, i receive this error in VStack Unable to infer complex closure return type; add explicit type to disambiguate.
My JSON convertor func looks like this:
let userResponse: [UserResponse] = load("users.json")
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
Custom data types:
struct UserResponse: Hashable, Codable, Identifiable {
public var id: Int
public var username: String
public var avatar: String
public var postOne: String
public var postTwo: String
public var postThree: String
public var postFour: String
public var story: String
public var likes: Int
public var commentNumber: Int
public var location: String
public var comment: String
public var name: String
public var followers: Int
public var following: Int
public var postNumbers: Int
public var bio: String
public var locationPic: String
}
I initialize it inside of a Child View:
var user : UserResponse
ZStack{
VStack{
ForEach(userResponse){user in
HStack(alignment: .center, spacing: 5){
PostCard(user: user)
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
PostCard(user: user)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))
}.shadow(color: Color("greySec"), radius: 3, x: 0, y: 0)
}
}.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarHidden(true)
}
My PostCard struct looks like this:
var user : UserResponse
VStack(spacing: 10){
HStack(spacing: 5){
VStack(spacing: 10){
Image(user.postOne)
.resizable().frame(height: 290)
HStack{
Text(user.comment)
.font(.system(size: 12, weight: .medium))
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5))
}
}
.background(Color("whiteAndBlack"))
.cornerRadius(5)
.foregroundColor(.black)
}
}
Question : Is there anyone who knows how to use JSON data inside of ForEach(){}? My goal is to create this view using various JSON data for each PostCard.

JSON decoder The data couldn’t be read because it isn’t in the correct format

I am new to this. Somehow I am able to understand how to do this.
I am doing below, but it's giving error- The data couldn’t be read because it isn’t in the correct format.Can someone help me with this? I am stuck on this from past 4 days. I really appreciate.
import SwiftUI
import Foundation
import Combine
struct Movie: Decodable, Identifiable {
var id: Int
var video: String
var vote_count: String
var vote_average: String
var title: String
var release_date: String
var original_language: String
var original_title: String
}
struct MovieList: Decodable{
var results: [Movie]
___________
class NetworkingManager : ObservableObject{
var objectWillChange = PassthroughSubject<NetworkingManager, Never>()
#Published var movies = [Movie]()
init() {
load()
}
func load(){
let url = URL(string: "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>")!
URLSession.shared.dataTask(with: url){ (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
self.movies = decodedLists
}
}else {
print("No Data")
}
} catch {
print (error.localizedDescription)
}
}.resume()
}
}
This is how the response looks like:
{
"page": 1,
"results": [
{
"id": 419704,
"video": false,
"vote_count": 1141,
"vote_average": 6.2,
"title": "Ad Astra",
"release_date": "2019-09-17",
"original_language": "en",
"original_title": "Ad Astra",
"genre_ids": [
878
],
"backdrop_path": "/5BwqwxMEjeFtdknRV792Svo0K1v.jpg",
"adult": false,
"overview": "An astronaut travels to the outer edges of the solar system to find his father and unravel a mystery that threatens the survival of Earth. In doing so, he uncovers secrets which challenge the nature of human existence and our place in the cosmos.",
"poster_path": "/xJUILftRf6TJxloOgrilOTJfeOn.jpg",
"popularity": 227.167,
"media_type": "movie"
},
]
}
Code should fetch the data and hold in it the array I created. So that I can use it to display in the front end.
I had to consume the exact same API for a similar project and this is how I did it.
When calling:
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
It needs to match the same properties that the JSON response returns.
Below you'll see a MovieResponse struct and the Movie class, which will list all of the properties and return types that the JSON response returns.
The type adopts Codable so that it's decodable using a JSONDecoder instance.
See this official example for more information regarding Codable.
A type that can convert itself into and out of an external representation.
Provided they match then the JSONDecoder() will work to decode the data.
ContentView.swift:
struct ContentView: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var body: some View {
MovieList(movie: self.movieViewModel.movie)
}
}
MovieListViewModel.swift:
public class MovieListViewModel: ObservableObject {
public let objectWillChange = PassthroughSubject<MovieListViewModel, Never>()
private var movieResults: [Movie] = []
var movie: MovieResults = [Movie]() {
didSet {
objectWillChange.send(self)
}
}
func load(url: String = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>") {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
DispatchQueue.main.async {
for movie in response.results {
self.movieResults.append(movie)
}
self.movie = self.movieResults
print("Finished loading Movies")
}
} catch {
print("Failed to decode: ", error)
}
}.resume()
}
}
MovieResponse.swift:
struct MovieResponse: Codable {
var page: Int
var total_results: Int
var total_pages: Int
var results: [Movie]
}
public class Movie: Codable, Identifiable {
public var popularity: Float
public var vote_count: Int
public var video: Bool
public var poster_path: String
public var id: Int
public var adult: Bool
public var backdrop_path: String
public var original_language: String
public var original_title: String
public var genre_ids: [Int]
public var title: String
public var vote_average: Float
public var overview: String
public var release_date: String
enum CodingKeys: String, CodingKey {
case popularity = "popularity"
case vote_count = "vote_count"
case video = "video"
case poster_path = "poster_path"
case id = "id"
case adult = "adult"
case backdrop_path = "backdrop_path"
case original_language = "original_language"
case original_title = "original_title"
case genre_ids = "genre_ids"
case title = "title"
case vote_average = "vote_average"
case overview = "overview"
case release_date = "release_date"
}
public init(popularity: Float, vote_count: Int, video: Bool, poster_path: String, id: Int, adult: Bool, backdrop_path: String, original_language: String, original_title: String, genre_ids: [Int], title: String, vote_average: Float, overview: String, release_date: String) {
self.popularity = popularity
self.vote_count = vote_count
self.video = video
self.poster_path = poster_path
self.id = id
self.adult = adult
self.backdrop_path = backdrop_path
self.original_language = original_language
self.original_title = original_title
self.genre_ids = genre_ids
self.title = title
self.vote_average = vote_average
self.overview = overview
self.release_date = release_date
}
public init() {
self.popularity = 0.0
self.vote_count = 0
self.video = false
self.poster_path = ""
self.id = 0
self.adult = false
self.backdrop_path = ""
self.original_language = ""
self.original_title = ""
self.genre_ids = []
self.title = ""
self.vote_average = 0.0
self.overview = ""
self.release_date = ""
}
}
public typealias MovieResults = [Movie]
MovieCellViewModel.swift:
public class MovieCellViewModel {
private var movie: Movie
public init(movie: Movie) {
self.movie = movie
}
public func getTitle() -> String {
return self.movie.title
}
// add more properties or functions here
}
MovieCell.swift:
struct MovieCell: View {
var movieCellViewModel: MovieCellViewModel
var body: some View {
Text(self.movieCellViewModel.getTitle())
}
}
MovieList.swift:
struct MovieList: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var movie: MovieResults
var body: some View {
List(self.movie) { movie in
MovieCell(movieCellViewModel: MovieCellViewModel(movie: movie))
}
}
}
I had a same error, but for the properties inside the struct for JSON object doesn't match JSON file. I fixed this error by setting the property names same as JSON file properties, and in the same order.
Swift file:
struct Coin: Decodable {
var asset_id_base: String
var rates: [CoinDetail]
}
struct CoinDetail: Decodable {
var time: String
var asset_id_quote: String
var rate: Double
}
JSON File:
{
"asset_id_base": "BTC",
"rates": [
{
"time": "2020-11-08T07:50:02.2865270Z",
"asset_id_quote": "CZK",
"rate": 328886.3419989546
},
{
"time": "2020-11-08T07:51:15.0750421Z",
"asset_id_quote": "GPL2",
"rate": 92123.4454168586
},
The Movie struct doesn't match the API response. You missed at least top level, like page and results array. Here you can paste your JSON answer and get the needed struct:
// MARK: - APIAnswer
struct APIAnswer: Codable {
let page: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let id: Int
let video: Bool
let voteCount: Int
let voteAverage: Double
let title, releaseDate, originalLanguage, originalTitle: String
let genreIDS: [Int]
let backdropPath: String
let adult: Bool
let overview, posterPath: String
let popularity: Double
let mediaType: String
enum CodingKeys: String, CodingKey {
case id, video
case voteCount = "vote_count"
case voteAverage = "vote_average"
case title
case releaseDate = "release_date"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case posterPath = "poster_path"
case popularity
case mediaType = "media_type"
}
}
Then you need to use the top level struct, like:
// ...
let decodeResult = try JSONDecoder().decode([APIAnswer].self, from: d)
let movieList = decodeResult.results
// the other advice: don't just take answer and put it to the array.
// API can have errors too, so you can get the array with 2 equal id, for example
update checked your code for second time: you use
let decodeResult = try JSONDecoder().decode([Movie].self, from: d)
instead of:
let decodeResult = try JSONDecoder().decode(MovieList.self, from: d)
P.S. better to attach also the full error from Xcode in future