Unable to extract data from Observable Object - ios

I'm trying to display data from an observed object.
Initially I tried to keep it simple, to merely display one member of a struct: 'base' (value: "stations").
struct Sample: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Double
// let sys: Sys
let id: Int
let name: String
// let cod: Int
}
I did some defensive coding with '?' but I got the following compiler error:
So I replaced the '?' with '!', knowing that I should have some data.
I find this frustrating, coming from an imperative-paradigm background.
What am I doing wrong?
Here's the data dump:
Sample(coord: DataTaskPubTab.Coord(lon: -0.13, lat: 51.51), weather:
[DataTaskPubTab.Weather(id: 300, main: "Drizzle", description: "light
intensity drizzle")], base: "stations", main:
DataTaskPubTab.Main(temp: 280.32, pressure: 1012, humidity: 81,
tempMin: 279.15, tempMax: 281.15), visibility: 10000, wind:
DataTaskPubTab.Wind(speed: 4.1, deg: 80), clouds:
DataTaskPubTab.Clouds(all: 90), dt: 1485789600.0, id: 2643743, name:
"London")
Here's the Observable Object:
class StandardWeatherReportLoader: ObservableObject {
#Published var networkMessage: String?
#Published var hasAlert = false
#Published var weatherReport: Sample?
#Published var hasReport = false
func doStandard() {
let url = EndPoint.weather.path()
var request = URLRequest(url: EndPoint.weather.path()!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: url!) { (data: Data?, _: URLResponse?, error: Error?) -> Void in
DispatchQueue.main.async {
guard error == nil else {
self.networkMessage = error?.localizedDescription
self.hasAlert = true
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Sample.self, from: data!)
self.weatherReport = result
self.hasReport = true
print(result)
} catch let error as NSError {
print(error)
}
}
}
task.resume()
}
}

Apparently this works:
struct StandardWeatherView: View {
#EnvironmentObject var settings: Settings
#ObservedObject var standardWeatherReportLoader = StandardWeatherReportLoader()
init() {
self.standardWeatherReportLoader.doStandard()
}
var body: some View {
NavigationView {
ZStack {
Color("FernGreen").edgesIgnoringSafeArea(.all)
VStack {
if standardWeatherReportLoader.weatherReport?.base == nil {
Text("Sorry, NO data.")
} else {
Text(standardWeatherReportLoader.weatherReport!.base)
}
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
.navigationBarTitle(Text("Weather Report"), displayMode: .inline)
}.alert(isPresented: $standardWeatherReportLoader.hasAlert, content: { () -> Alert in
Alert(title: Text(verbatim: standardWeatherReportLoader.networkMessage!))
})
}
}
}

Very probably the web services data is not yet available when the view renders for the first time. So you shouldn't force unwrap the value with !. If you want to create a Text() element in both cases, you can simply use the ?? operation (see Nil-Coalescing Operator in the Swift language documentation):
Text(standardWeatherReportLoader.weatherReport?.base ?? "Sorry, NO data.")

Related

Swift JSON Nested out put for ListView

Iam trying to display the contents of the result.
The Data is returned as JSON Array.
I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.
Help would be highly appreciated
import SwiftUI
struct Stocks: Hashable, Codable{
var results: [Results]
var status: String
struct Results: Hashable, Codable{
var ticker: String
var name: String
var market: String
var locale: String
var primary_exchange: String
var type: String
var active: Bool
var currency_name: String
var cik: String
var composite_figi: String
var share_class_figi: String
var last_update_utc: String
}
}
class ViewModel: ObservableObject{
#Published var stocks: [Stocks] = []
func fetch(){
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
let task = URLSession.shared.dataTask(with: url) {[weak self]data, _, error in
guard let data = data, error == nil else{
return
}
// Convert JSON
do{
let stocks = try JSONDecoder().decode([Stocks].self, from: data)
DispatchQueue.main.async{
self?.stocks = stocks
}
}
catch{
print(error)
}
}
task.resume()
}
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
NavigationView{
List{
ForEach(viewModel.stocks, id: \.self){resu in
ForEach(resu.results, id: \.self){st in
Text(st.currency_name)
}
}
}
}.navigationTitle("Stocks")
.onAppear{
viewModel.fetch()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the Response object:
{
"results": [
{
"ticker": "A",
"name": "Agilent Technologies Inc.",
"market": "stocks",
"locale": "us",
"primary_exchange": "XNYS",
"type": "CS",
"active": true,
"currency_name": "usd",
"cik": "0001090872",
"composite_figi": "BBG000C2V3D6",
"share_class_figi": "BBG001SCTQY4",
"last_updated_utc": "2022-12-20T00:00:00Z"
},
{
"ticker": "AA",
"name": "Alcoa Corporation",
"market": "stocks",
"locale": "us",
"primary_exchange": "XNYS",
"type": "CS",
"active": true,
"currency_name": "usd",
"cik": "0001675149",
"composite_figi": "BBG00B3T3HD3",
"share_class_figi": "BBG00B3T3HF1",
"last_updated_utc": "2022-12-20T00:00:00Z"
},
I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.
The naming of your structs is largely confusing.
According to the JSON you are going to receive one root object containing an array of Stock (supposed to be named in singular form) objects for key results.
And there is a struct member last_update_utc which does not match the key last_updated_utc.
Name your structs this way, I renamed the struct members as camelCase and as constants (let) and you can decode lastUpdatedUtc as Date with the .iso8601 strategy
struct Response: Hashable, Decodable {
let results: [Stock]
let status: String
struct Stock: Hashable, Decodable {
let ticker: String
let name: String
let market: String
let locale: String
let primaryExchange: String
let type: String
let active: Bool
let currencyName: String
let cik: String
let compositeFigi: String
let shareClassFigi: String
let lastUpdatedUtc: Date
}
}
and decode the JSON
class ViewModel: ObservableObject{
#Published var stocks: [Response.Stock] = []
func fetch() {
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
let task = URLSession.shared.dataTask(with: url) {[weak self] data, _, error in
if let error { print(error); return }
// Convert JSON
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let response = try decoder.decode(Response.self, from: data!)
DispatchQueue.main.async {
self?.stocks = response.results
}
}
catch{
print(error)
}
}
task.resume()
}
}
I even recommend to use async/await
#MainActor
class ViewModel: ObservableObject{
#Published var stocks: [Response.Stock] = []
func fetch() {
guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let response = try decoder.decode(Response.self, from: data)
self.stocks = response.results
} catch {
print(error)
}
}
}
}

SwiftUI : Can't receive API response

I'm trying to get a response from this link : https://zsr.octane.gg/players/5f5ae840c6cbf591c568a477 but it won't work and I can't figure why.
There is my ContentView.swift :
struct ContentView: View {
#State private var players = [Player]()
var body: some View {
List(players, id: \._id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.country)
}
}.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://zsr.octane.gg/players/5f5ae840c6cbf591c568a477") else {
print("URL invalide")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
players = decodedResponse.players
}
} catch {
print("Invalid data")
}
}
}
My Response struct :
struct Response: Codable {
var players: [Player]
}
Player struct :
struct Player: Codable {
var _id: String
var slug: String
var tag: String
var name: String
var country: String
var team: Team
var accounts: [Account]
var revelant: Bool
}
Team struct :
struct Team: Codable {
var _id: String
var slug: String
var name: String
var region: String
var image: String
var relevant: Bool
}
Account struct :
struct Account: Codable {
var platform: String
var id: String
}
Edit: the error that I have from the do catch is :
Invalid data with error: keyNotFound(CodingKeys(stringValue: "revelant", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "revelant", intValue: nil) ("revelant").", underlyingError: nil))
I followed a tutorial which works well but when I replace the link and use my own structs nothing happens when I launch the app.
Thanks for your help.
Your response is a dictionary not an array to to log error use try instead of try? and log the error inside the catch part , this snippet is after the edit and it shows currently info of 1 player
One player
func loadData() async {
guard let url = URL(string: "https://zsr.octane.gg/players/5f5ae840c6cbf591c568a477") else {
print("URL invalide")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decodedResponse = try JSONDecoder().decode(Player.self, from: data)
players = [decodedResponse]
} catch {
print("Invalid data with error: ",error)
}
}
All players
May be you mean to drop 5f5ae840c6cbf591c568a477 from guard let url = URL(string: "https://zsr.octane.gg/players/5f5ae840c6cbf591c568a477") else { to get all players , check here complete working demo
import SwiftUI
struct ContentView: View {
#State private var players = [Player]()
var body: some View {
List(players, id: \._id) { item in
VStack(alignment: .leading) {
Text(item.name ?? "")
.font(.headline)
Text(item.country ?? "")
}
}.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://zsr.octane.gg/players") else {
print("URL invalide")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
players = decodedResponse.players
} catch {
print("Invalid data",error)
}
}
}
struct Response: Codable {
var players: [Player]
}
struct Player: Codable {
var _id: String
var slug: String
var tag: String
var name: String?
var country: String?
var accounts: [Account]?
}
struct Team: Codable {
var _id: String
var slug: String
var name: String
var region: String
var image: String
var relevant: Bool
}
struct Account: Codable {
var platform: String
var id: String
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Note: a variable that may exist in a model or not should be marked as optional

Swift JSON Parsing issue - Possible problem with struct?

Upon login and validation, the user is sent to the main application page. I have the following code set.
import SwiftUI
typealias MyDefendant = [Defendant]
struct ContentView: View {
var email: String
#State var myDefendant: MyDefendant = []
func getUserData(completion:#escaping (MyDefendant)->()) {
var urlRequest = URLRequest(url: URL(string: "https://milanobailbonds.com/getDefendant.php")!)
urlRequest.httpMethod = "post"
let authData = [
"defEmail" : email
] as [String : Any]
do {
let authBody = try JSONSerialization.data(withJSONObject: authData, options: .prettyPrinted)
urlRequest.httpBody = authBody
urlRequest.addValue("application/json", forHTTPHeaderField: "content-type")
} catch let error {
debugPrint(error.localizedDescription)
}
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else {
return
}
do {
let responseString = String(data: data, encoding: .utf8)!
print(responseString)
var returnValue: MyDefendant?
let decoder = JSONDecoder()
returnValue = try decoder.decode([Defendant].self, from: data)
completion(returnValue!)
}
catch { fatalError("Couldn't Parse")
}
}.resume()
return
}
var body: some View {
NavigationView {
VStack {
Text(email)
Text("I Need Bail")
.font(.largeTitle)
.fontWeight(.semibold)
Button {
print("Test")
} label: {
Label("I Need Bail", systemImage: "iphone.homebutton.radiowaves.left.and.right")
.labelStyle(IconOnlyLabelStyle())
.font(.system(size: 142.0))
}
} .foregroundColor(.green)
.shadow(color: .black, radius: 2, x: 2, y: 2)
.navigationBarTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading:
Button {
print("Test")
} label: {
Label("I Need Bail", systemImage: "line.3.horizontal")
.labelStyle(IconOnlyLabelStyle())
})
}.onAppear() {
getUserData() { myDefendant in
self.myDefendant = myDefendant
}
}
}
}
In my data models I have created a struct for Defendant as such:
struct Defendant: Codable, Hashable, Identifiable {
var id: Int
var defImage: String
var defName: String
var defAddress: String
var defCity: String
var defState: String
var defZip: String
var defPhone: String
var defEmail: String
var defUserName: String
var defPW: String
var defDOB: String
var defPriorFTA: Int
var defFTAExplained: String
var defAssignedAgency: Int
} // Defendant Model
The PHP is working fine and returning valid JSON with all of the required items for the struct.
"\"[\\n {\\n \\\"Id\\\": 5,\\n \\\"defImage\\\": \\\"\\\",\\n \\\"defName\\\": \\\"Some Dude\\\",\\n \\\"defAddress\\\": \\\"123 Main St\\\",\\n \\\"defCity\\\": \\\"Some City\\\",\\n \\\"defState\\\": \\\"FL\\\",\\n \\\"defZip\\\": \\\"12345\\\",\\n \\\"defPhone\\\": \\\"888-888-8888\\\",\\n \\\"defEmail\\\": \\\"someone#someone.com\\\",\\n \\\"defUserName\\\": \\\"\\\",\\n \\\"defPW\\\": \\\"91492cffa4032765f6b025ec6b2c873e49fe5e58\\\",\\n \\\"defDOB\\\": \\\"01\\\\\\/01\\\\\\/1955\\\",\\n \\\"defPriorFTA\\\": 0,\\n \\\"defFTAExplained\\\": \\\"\\\",\\n \\\"defAssignedAgency\\\": 0\\n }\\n]\""
Unfortunately, I keep getting an error "Unable to Parse".
I'm new to Swift, and coding in general.
Any thoughts or ideas are greatly appreciated.
Thank you
Im not sure but you can try to change id into Id in your struct. Please remember name of your struct property must exactly the same with key in Json response.

SwiftUI List doesn't appear

Good morning,
I have an issue with my SwiftUI list.
After receiving the data from my JSON correctly and having it as a string afterwards, the informations doesn't seem to appear in my list view.
struct Lists: View{
#State private var Countries = [Country]()
var body: some View {
List(Countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.Country)
.font(.headline)
Text(item.Country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
let jsonData = (try! String(contentsOf: url))
/*print(jsonData)*/
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.Countries)
} catch let error as NSError {
/*print("json error: \(error.localizedDescription)")*/
print("json error: \(error)")
}
}
}.resume()
}
Here are my structs for the object:
struct AllCountries: Decodable {
var Countries: [Country]
}
struct AllCountries: Decodable {
var Countries: [Country] }
struct Country: Decodable, Identifiable {
let id = UUID()
var Country, CountryCode, Slug: String
var NewConfirmed, TotalConfirmed, NewDeaths, TotalDeaths: Int
var NewRecovered, TotalRecovered: Int
var Date: Date
}
enum CodingKeys: String, CodingKey {
case Country = "Country"
case CountryCode = "CountryCode"
case Slug = "Slug"
case NewConfirmed = "NewConfirmed"
case TotalConfirmed = "TotalConfirmed"
case NewDeaths = "NewDeaths"
case TotalDeaths = "TotalDeaths"
case NewRecovered = "NewRecovered"
case TotalRecovered = "TotalRecovered"
case Date = "Date"
}
}
Here is the beginning of the result of the "data" when I print it:
[_IOS_Project.Country(id: EB629D42-8278-444C-878E-A6EAC46BD5D6, Country: "Afghanistan", CountryCode: "AF", Slug: "afghanistan", NewConfirmed: 546, TotalConfirmed: 28424, NewDeaths: 21, TotalDeaths: 569, NewRecovered: 330, TotalRecovered: 8292, Date: 2020-06-21 19:37:01 +0000), _IOS_Project.Country(id: 8DDDCA84-CE99-4374-A487-096BFDF8A467, Country: "Albania", CountryCode: "AL", Slug: "albania", NewConfirmed: 53, TotalConfirmed: 1891, NewDeaths: 1, TotalDeaths: 43, NewRecovered: 12, TotalRecovered: 1126, Date: 2020-06-21 19:37:01 +0000),
Could somebody point me to the right direction on this issue?
Thanks in advance :)
There seems to be a few problems with your code.
Firstly the naming of your variables. Variable names in Swift begin with a lowercase, structs and classes begin with uppercase.
Secondly you aren't assigning the response from the your URLRequest to the countries state variable, this is the main problem.
Thirdly, your pasted code doesn't seem to be formatted correctly.
I put your code into a fresh project and with a few tweaks I got it to work.
struct ContentView: View {
#State private var countries = [AllCountries.Country]()
var body: some View {
List(countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.country)
.font(.headline)
Text(item.country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.countries)
// You should update this on the main thread
DispatchQueue.main.async {
// this assigns the values you received to the state variable countries
self.countries = decodedResponse.countries
}
} catch let error as NSError {
print("json error: \(error)")
}
}
}.resume()
}
}
Notice in the loadData function that I assign the response from the URLRequest to the countries variable. Because this is a #State variable it causes the screen to reload when it changes. You weren't doing this so your UI had no idea that it needed to update.
I also updated your variable names, so that they are lowercased.
struct AllCountries: Decodable {
var countries: [Country]
enum CodingKeys: String, CodingKey {
case countries = "Countries"
}
struct Country: Decodable, Identifiable {
let id = UUID()
var country, countryCode, slug: String
var newConfirmed, totalConfirmed, newDeaths, totalDeaths: Int
var newRecovered, totalRecovered: Int
var date: Date
enum CodingKeys: String, CodingKey {
case country = "Country"
case countryCode = "CountryCode"
case slug = "Slug"
case newConfirmed = "NewConfirmed"
case totalConfirmed = "TotalConfirmed"
case newDeaths = "NewDeaths"
case totalDeaths = "TotalDeaths"
case newRecovered = "NewRecovered"
case totalRecovered = "TotalRecovered"
case date = "Date"
}
}
}

How do i call the parsed data from the GET request that have a longer nested JSON structure in Swift?

NOTE: Forgive my cluelessness, i am still new in regards to this. The full code is posted at the bottom.
ISSUE: It seems that when i have a short nest, i am able to call it for my #Published property however when i try an api request with a longer nest, like this. and type Decodable structs that follows the structure of the GET request
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
I am not able to call it for the #Published var dataSet1 = [TripTest]()
self.dataSet1 = tripJSON.TripList.Trip.Leg
I get an error message, that says "Value of type '[TravelDetail]' has no member 'Leg'
I am not sure why, however it works when i use [TravelDetail]() instead of [TripTest]() in the #Published var and stop at Trip before Leg for the dataSet1, then it seems to at least build successfully. But now i am not able to get the name and type information from the request
Full code
import SwiftUI
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
class TripViewModel: ObservableObject {
#Published var dataSet1 = [TripTest]()
init() {
let urlString = "http://xmlopen.rejseplanen.dk/bin/rest.exe/trip?originId=8600790&destId=6553&format=json"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
guard let data = data else { return }
do {
let tripJSON = try
JSONDecoder().decode(TripScheduleTest.self, from: data)
print(data)
DispatchQueue.main.async {
self.dataSet1 = tripJSON.TripList.Trip.Leg
}
} catch {
print("JSON Decode error: ", error)
}
}.resume()
}
}
struct TripView: View {
#ObservedObject var vm = TripViewModel()
var body: some View {
List(vm.dataSet1, id: \.self) { day in
Text("Test")
.font(.system(size: 12, weight: .bold))
Text(" \(day.name)")
.font(.system(size: 12))
}
}
}
Trip is an array (note the [])
You need to get one item of the array by index for example
tripJSON.TripList.Trip.first?.Leg
To assign the value to a non-optional array write
self.dataSet1 = tripJSON.TripList.Trip.first?.Leg ?? []

Resources