I have read topics on SO but not found an answer yet.
I have been trying to use a weather api to download weather data for my app. Strangely I can run it on urls without a '?' but this url has a '?' built in. I suspect this is the problem but how do I fix it, or get it to ignore it? That is my theory anyhow. Heres the code:
struct WeatherData: Decodable {
let description: String
let temp: Double
let wind: Double
}
func weather() {
let url = "http://api.openweathermap.org/data/2.5/weather?q=London,GB?&units=imperial&APPID={40b5f59a0004885043fe3df3e0b6ed8e}"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do {
let weatherObj = try JSONDecoder().decode([WeatherData].self, from: data!)
print(data!)
for weather in weatherObj {
print(weather.temp, weather.description, weather.wind)
}
} catch {
print("Got an Error")
}
}.resume()
}
So running that as is produces the error: "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" or the URLSession line.
Am I missing something really obvious or is there a way to fix this?
Many thanks
--
Updated:
So after changing the structs and {} it was working until I began to enter data into labels. Heres the latest attempt:
func weather() {
let lat = locationManager.location!.coordinate.latitude
let long = locationManager.location!.coordinate.longitude
//let baseURL = "http://api.openweathermap.org/data/2.5/weather?"
let apiKey = "40b5f59a0004885043fe3df3e0b6ed8e"
//let weatherURL = URL(string: "\(baseURL)lat=\(lat)&lon=\(long)&units=metric&APPID=\(apiKey)")
let weahterURL = "http://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(long)&units=metric&APPID=\(apiKey)"
//let url = "http://api.openweathermap.org/data/2.5/weather?q=London,GB?&units=imperial&APPID=40b5f59a0004885043fe3df3e0b6ed8e"
let urlObj = URL(string: weahterURL)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do {
let weatherObj = try JSONDecoder().decode(WeatherData.self, from: data!)
print(weatherObj)
//seems as though not gettign any data from beyond this point
var desc = weatherObj.weather
var wind = weatherObj.wind.speed
var tempMin = weatherObj.main.temp_min
var tempMax = weatherObj.main.temp_max
DispatchQueue.main.async {
self.weatherDesc = desc
self.weartherWind.text = wind
self.tempMinTxt.text = tempMin
self.tempMaxTxt.text = tempMax
}
} catch {
print("Got an Error", error.localizedDescription)
}
}.resume()
}
You mistakenly construct the url instead of
let url = "http://api.openweathermap.org/data/2.5/weather?q=London,GB?&units=imperial&APPID={40b5f59a0004885043fe3df3e0b6ed8e}"
do
let url = "http://api.openweathermap.org/data/2.5/weather?q=London,GB?&units=imperial&APPID=40b5f59a0004885043fe3df3e0b6ed8e"
This
{40b5f59a0004885043fe3df3e0b6ed8e}
should be
40b5f59a0004885043fe3df3e0b6ed8e
Also the struct you create for the decoder is not valid and won't get the data
//
struct WeatherData: Decodable {
let weather: [WeatherItem]
let wind: WindItem
let main : MainItem
}
//
struct WeatherItem: Decodable {
let description: String
}
//
struct WindItem: Decodable {
let speed: Double
let deg: Double
}
//
struct MainItem : Decodable {
let tempMin: Double
let tempMax: Double
private enum CodingKeys: String, CodingKey {
case tempMin = "temp_min" , tempMax = "temp_max"
}
}
//
let weatherObj = try JSONDecoder().decode(WeatherData.self, from: data!)
Related
Goals
Accessing the Dictionaries within an array
Some how get all 1957 Dictionaries decoded without hand coding each ticker name.
The below image is data from https://rapidapi.com/Glavier/api/binance43/ to replicate the below image get Symbol Price Ticker needs to be selected.
With the help of another question which was answered here I have included code below which I am trying to change to accomplish the above goals.
CallApi.swift - this file calls the API and models it to PriceApiModel
import UIKit
class ViewController: UIViewController {
let headers = [
"X-RapidAPI-Key": "Sorry I cannot include this",
"X-RapidAPI-Host": "binance43.p.rapidapi.com"
]
let request = NSMutableURLRequest(url: NSURL(string: "https://binance43.p.rapidapi.com/ticker/price")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
func getData() {
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print("error")
} else {
let httpResponse = response as? HTTPURLResponse
do {
//let dictionary = try JSONSerialization.jsonObject(with: data!, options: [])
let model = try JSONDecoder().decode(PriceApiModel.self, from: data!)
//print(String(model.symbol) + "name") // please see output below
//print(dictionary)
} catch {
print("NOT WORKING ")
}
}
})
dataTask.resume()
}
}
PriceApiModel.swift - I am trying to find a way for this file to be a model for decoding the data
struct PriceApiModel: Hashable, Codable {
//changed the String type to Decimal
var price: String
// every property you are interested to decode needs a CodingKey.
// You can omit values you are not interested in
enum CodingKeys: CodingKey{
case askPrice
}
// here you decode your data into the struct
init(from decoder: Decoder) throws {
// get the container
let container = try decoder.container(keyedBy: CodingKeys.self)
// decode the askPrice into a String and cast it into a Decimal
let askPrice = String(try container.decode(String.self, forKey: .askPrice))
// check if casting was succesfull else throw
guard let askPrice = askPrice else{
throw CustomError.decodingError
}
// assign it
self.askPrice = askPrice
}
}
So I just tried out what you want to achieve here. First of all, you declared a service class (fetching data) as ViewController, by inheritance a UIViewController. It seems to me a bit odd just having this in a class because the UIViewController is not used. Secondly, I would recommend you to watch or read something about Codable for example Hackingforswift. It helped at least me :)
However, here is a Code that shows you a way how it could work:
OptionalObject is needed because of the data structure, holding everything within an array.
struct OptionalObject<Base: Decodable>: Decodable {
public let value: Base?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.value = try container.decode(Base.self)
} catch {
self.value = nil
}
}
}
struct PriceApiModel: Codable {
let price: String
let symbol: String
}
enum ServiceError: Error {
case failureAtDecoding
}
// MVVM Pattern https://www.hackingwithswift.com/books/ios-swiftui/introducing-mvvm-into-your-swiftui-project
class ServiceViewModel: ObservableObject {
// Publisher you can subscribe to it.
// Every time the Publisher changes view will re-render.
#Published var priceModel: [PriceApiModel] = []
let headers = [
"X-RapidAPI-Key": "",
"X-RapidAPI-Host": "binance43.p.rapidapi.com"
]
var request = URLRequest(
url: URL(string: "https://binance43.p.rapidapi.com/ticker/price")!,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 5.0
)
init() {
self.getData { priceModel in
// As DocC says:
/// A value that represents either a success or a failure, including an
// So you have to "unwrap" it to handle success or failure
switch priceModel {
case let .success(result):
DispatchQueue.main.async {
self.priceModel = result
}
case let .failure(failure):
print(failure)
}
}
}
func getData(priceModel: #escaping (Result<[PriceApiModel], Error>) -> Void) {
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { (data, response, error) -> Void in
if let error = error {
priceModel(.failure(error))
} else if let data = data {
let model = try? JSONDecoder().decode([OptionalObject<PriceApiModel>].self, from: data)
let editModel = model?.compactMap {
PriceApiModel(price: $0.value?.price ?? "nil", symbol: $0.value?.symbol ?? "nil")
}
if let editModel = editModel {
priceModel(.success(editModel))
} else {
priceModel(.failure(ServiceError.failureAtDecoding))
}
}
}
dataTask.resume()
}
}
struct ContentView: View {
// Initialize the ServiceViewModel as StateObject
#StateObject var viewModel: ServiceViewModel = .init()
var body: some View {
NavigationView {
List {
ForEach(viewModel.priceModel, id: \.symbol) { model in
HStack {
Text(model.symbol)
Spacer()
Text(model.price)
}
}
}
}
}
}
Hope I could help.
I got this from the codingwithchris.com Xcode tutorial, which is pretty good. I tried to substitute the URL of my xml-formatted URL in place of Chris's json-formatted newsapi.org URL.
As expected I got "Error parsing the json". Grateful for any quick tips.
let stringUrl = "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=8c4d5faa662f4dce849d17d89e86ca14"
let url = URL(string: stringUrl)
guard url != nil else {
print("Couldn't create url object")
return
}
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let articleService = try decoder.decode(ArticleService.self, from: data!)
if articleService.articles == nil {
return
}
let articles = articleService.articles!
DispatchQueue.main.async {
self.delegate?.articlesRetrieved(articles)
} // end DispatchQueue.main.sync
} end do
catch {
print("Error parsing the json")
} // End catch
} // End if
} // End dataTask
dataTask.resume()
Most probably you are using an incorrect structure for decoding the json. Here is what you need.
struct Response: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title, description, url, urlToImage: String
let publishedAt: String
let content: String?
}
struct Source: Codable {
let id: String?
let name: String
}
I am new to Swift and trying basic JSON parsing by following tutorials. I want to print a field of a JSON file, but it is not working.
Although the link exists, and I am using the same link I used for a previous tutorial, it returns rather than moved on to accessing the JSON.
I understand there is an "easier" way to do it in Swift4 using Decoder, but I received an error when I did it that way.
Here is the structure I am using:
struct Tester {
var userId: Int
var id: Int
var title: String
var body: String
init(json: [String: Any]){
userId = json["userId"] as? Int ?? -10
id = json["id"] as? Int ?? -400
title = json["title"] as? String ?? ""
body = json["body"] as? String ?? ""
}
}
And here is the code that is trying to access the JSON entries
#IBAction func printIDTitle(_ sender: Any) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
guard let data = data else { return }
do {
print("here 0\n")
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
print(error)
return
}
print("here 0.5\n")
print("here 1\n")
let d = Tester(json: json)
print(d.id)
print(d.title)
print("here 2\n")
} catch let error {
print(error)
}
}.resume()
}
The "here 0" is the only print that shows up.
What could be my issue?
The root is an array so change
guard let json = try JSONSerialization.jsonObject(with: data, options:[]) as? [[String: Any]] else {
print(error)
return
}
Or better
let res = try! JSONDecoder().decode([Root].self, from:data)
struct Root: Codable {
let userId, id: Int
let title, body: String
}
I am working through the Apple App Development Guide and this is the code I am working with right now...
struct CategoryInfo: Codable {
var category: String
var description: String
var logo: String
var mobileCategoryName: String
enum Keys: String, CodingKey {
case category
case description = "descr"
case logo
case mobileCategoryName = "mobileCatName"
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: Keys.self)
self.category = try valueContainer.decode(String.self, forKey: Keys.category)
self.description = try valueContainer.decode(String.self, forKey: Keys.description)
self.logo = try valueContainer.decode(String.self, forKey: Keys.logo)
self.mobileCategoryName = try valueContainer.decode(String.self, forKey: Keys.mobileCategoryName)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let categories = Industry_TableViewController()
categories.fetchCategoryInfo { (category) in
if let category = category {
print(category)
}
}
}
func fetchCategoryInfo(completion: #escaping(CategoryInfo?) -> Void) {
let url = URL(string: "XXXXX")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let category = try? jsonDecoder.decode(CategoryInfo.self, from: data) {
completion(category)
} else {
print("Nothing reutrned or Not decoded")
completion(nil)
}
}
task.resume()
}
it works fine when my returned JSON is in the following format...
{"category":"Excavators","descr":"Compact, Mid-Sized, Large, Wheeled, Tracked...","logo":"excavators","mobileCatName":"Excavators"}
My struct is created and all the variables are populated correctly. But the API doesn't bring back one category at a time it brings back multiple like so...
[{"category":"Aerial Lifts","descr":"Aerial Lifts, Man Lifts, Scissor Lifts...","logo":"aeriallifts","mobileCatName":"Aerial Lifts"},{"category":"Aggregate Equipment","descr":"Crushing, Screening, Conveyors, Feeders and Stackers...","logo":"aggregateequipment","mobileCatName":"Aggregate"},{"category":"Agricultural Equipment","descr":"Tractors, Harvesters, Combines, Tillers...","logo":"agricultural","mobileCatName":"Agricultural"}]
And I am running into a wall trying to figure out how to get this decoded properly. I've gone down so many routes I don't even know what to search for any more. Can anyone help or point me in a direction.
You need to modify your function to parse an array of categories instead of a single one. You just need to pass the Array<CategoryInfo> metatype to the decode function and modify the function signature such that the completion handler also returns an array.
func fetchCategoryInfo(completion: #escaping ([CategoryInfo]?) -> Void) {
let url = URL(string: "XXXXX")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let categories = try? jsonDecoder.decode([CategoryInfo].self, from: data) {
completion(categories)
} else {
print("Nothing reutrned or Not decoded")
completion(nil)
}
}
task.resume()
}
try? jsonDecoder.decode([CategoryInfo].self, from: data)
I am trying to pull car information from the following API.
but I can't seem to display the information in my tableview...
Any and all help is appreciated!
viewController
var hondaList: [HondaModel] = []
override func viewDidLoad() {
//let jsonUrl = "https://api.myjson.com/bins/149ex5"
let url = URL(string: "https://api.myjson.com/bins/149ex5")
URLSession.shared.dataTask(with: url!) { (data, urlrespone , error) in
do{
try self.hondaList = JSONDecoder().decode([HondaModel].self, from: data!)
for honda in self.hondaList {
print(honda.name)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch{
print( "Error in fectching from https://api.myjson.com/bins/149ex5")
}
}.resume()
super.viewDidLoad()
}
Model
import Foundation
struct HondaModel: Decodable {
let name: String
let engine: String
let transmission: String
let ocolor: String
let icolor: String
let vin: String
}
This is a very common mistake: You are ignoring the root object (and both possible errors)
Add this struct
struct Root : Decodable {
private enum CodingKeys: String, CodingKey { case results = "Results", message = "Message" }
let results : [HondaModel]
let message : String
}
and decode
if let error = error { print(error); return }
do {
let root = try JSONDecoder().decode(Root.self, from: data!)
self.hondaList = root.results
...
and please, please, print the error rather than a meaningless literal string. The error tells you what's wrong.
catch {
print(error)
}
In your case you would get
"Expected to decode Array<Any> but found a dictionary instead."
which is a very significant hint.
try this
if let resultJSON = data?["Results"] as? [[String: Any]] {
do {
let _data = try JSONSerialization.data(withJSONObject: resultJSON, options: .prettyPrinted)
self.hondaList = try JSONDecoder().decode([HondaModel].self, from: _data)
// … same thing
}
}