How to get a UIImage from a String with JSON data - ios

I am trying to load an icon image form the API OpenWeatherMap, and display it in an ImageView. I am trying to load it into the imageView 'iconImage'. I have successfully loaded the JSON data for the location and humidity, as they are Strings, but the Icon data is also a String and I cannot get it to display as a UIImage.
Code below:
My JSON Structs below:
struct Coordinate : Decodable {
let lat, lon : Double?
}
struct Weather : Decodable {
var id : Int?
var main, myDescription, icon : String?
enum CodingKeys : String, CodingKey {
case id = "id"
case main = "main"
case icon = "icon"
case myDescription = "description"
}
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct MyWeather : Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather : [Weather]?
let sys : Sys?
let main : Main?
let wind : Wind?
let dt : Date?
}`enter code here`
View controller below:
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
if (self.iconImage != nil)
{
if let gicon = weatherData.weather?.first?.icon {
DispatchQueue.main.async {
self.iconImage.image! = gicon
}
}
}
if (self.LocationLabel != nil)
{
if let gmain = weatherData.name {
print(gmain)
DispatchQueue.main.async {
self.LocationLabel.text! = "Current Weather in: " + String (gmain)
}
}
}
if (self.HumidityLabel != nil)
{
if let ghumidity = weatherData.main?.humidity
{
print(ghumidity, "THIS IS HUMIDITY")
DispatchQueue.main.async {
self.HumidityLabel.text! = String (ghumidity)
}
}
}

Use Kingfisher
It creates an extension in ImageView. You will use self.imageview.kf.setImage(with: "url")

Icon is the id of the image , you need to append it to this url and load it
http://openweathermap.org/img/w/10d.png // here ------ id = 10d
suppose you'll use SDWebImage , then do this
let urlStr = "http://openweathermap.org/img/w/\(gicon).png"
self.iconImage.sd_setImage(with: URL(string:urlStr), placeholderImage: UIImage(named: "placeholder.png"))
See here in Docs

Related

How to get value from Optional(Optional(<__NSSingleObjectArrayI >(25)))

I am trying to get the value of "price" key which is "25"
I am getting this response Json From Backend
{
"errorCode": 0,
"message": "Request successfully served.",
"data": {
"games": {
"TWELVEBYTWENTYFOUR": {
"jackpot_amount": "KES 40,000.00",
"draw_date": "2021-05-21 10:59:45",
"extra": {
"jackpotAmount": 40000,
"unitCostJson": [
{
"currency": "KES",
"price": 25
}
]
},
}
},
"currentTime": {
"date": "2021-05-20 22:28:18.738038"
}
}
}
This is my code so far :
fetchData { (dict, error) in
let playerLoginInfo = dataDict["data"] as? NSDictionary
let playerGameInfo = playerLoginInfo?.value(forKey: "games") as? NSDictionary
if let TWELVEBYTWENTYFOUR = playerGameInfo?.value(forKey: "TWELVEBYTWENTYFOUR") as? NSDictionary {
let extra = TWELVEBYTWENTYFOUR.value(forKey: "extra") as? NSDictionary
let unitCostJson = extra?.value(forKey: "unitCostJson") as? NSArray
print("price")
print(unitCostJson?.value(forKey: "price") as? Any)
}
}
I get this is console :
Optional(Optional(<__NSSingleObjectArrayI 0x600001f091d0>(
25
)
))
I have seen this question How can I access values within Optional NSSingleObjectArrayI? but I couldn't figure out a solution
Edit:
I have now used Codeable to get data:
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: Games
let currentTime: CurrentTime
}
struct Games: Codable {
let game_code: String
let datetime: String
let estimated_jackpot: String
let guaranteed_jackpot: String
let jackpot_title: String
let jackpot_amount: String
let draw_date: String
let extra: Extra
let next_draw_date: String
let active: String
}
struct Extra: Codable {
let currentDrawNumber: Int
let currentDrawFreezeDate: String
let currentDrawStopTime: String
let jackpotAmount: Int
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
let timezone_type: Int
let timezone: String
}
I'm trying to get value from price now with this code
do{
let resp:Resp = try JSONDecoder().decode(Resp.self , from:data);
let data = resp.data
let games = data.games
let extra = games.extra
let unitCostJson = extra.unitCostJson
print(unitCostJson[0].price)
}
catch{
GlobalFunctions.shared.callOnMainThread {
self.showAlert(Message: "Something went wrong. Please retry.")
}
}
It is going into catch
How should I get the data inside on the unitCostJson now??
I butchered your struct and removed any irrelevant properties (compared to the json), if you want to add them back then you need to use an CodingKey enum
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: [String:Games]
let currentTime: CurrentTime
}
struct Games: Codable {
let extra: Extra
}
struct Extra: Codable {
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
}
Now you can access the unitCost like this
let unitCost = resp.data.games["TWELVEBYTWENTYFOUR"]?.extra.unitCostJson

Not able to parse json without model

I'm making an api call and getting the response like so..
if let data = NSData(contentsOf: NSURL(string: "http://test.chatongo.in/testdata.json")! as URL) {
do {
if let response = try JSONSerialization.jsonObject(with: data as Data, options: []) as? NSDictionary {
print("THE RESPONSE IS: \(response)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
And the response I get like so...
THE RESPONSE IS: {
Message = Success;
Status = 200;
data = {
Records = (
{
Id = 1;
collectedValue = 500;
endDate = "10/06/2018";
mainImageURL = "http://iphonedeveloperguide.com/oneinr/project1.jpg";
shortDescription = "This foundation will bring smile on there faces";
startDate = "05/05/2018";
title = "Smile Crowdfunding";
totalValue = 5000;
},
{
Id = 2;
collectedValue = 750;
endDate = "08/06/2018";
mainImageURL = "http://iphonedeveloperguide.com/oneinr/project10.jpg";
shortDescription = "This foundation will help animals";
startDate = "05/05/2018";
title = "Animal Funding";
totalValue = 20000;
}
);
TotalRecords = 10;
};
}
But how do I parse this json and get the individual elements out of it including the image, that I'm not able to figure out.
You need
import UIKit
class ViewController: UIViewController {
var records = [Record]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
URLSession.shared.dataTask(with: URL(string: "http://test.chatongo.in/testdata.json")!) { (data, response, error) in
guard let data = data else { return }
do {
let res = try JSONDecoder().decode(Root.self, from: data)
self.records = res.data.records
print(res.data.records)
// if it's a collection/table wrap the reload here inside DispatchQueue.main.async
}
catch {
print(error)
}
}.resume()
}
}
// MARK: - Empty
struct Root: Codable {
let status: Int
let message: String
let data: DataClass
enum CodingKeys: String, CodingKey {
case status = "Status"
case message = "Message"
case data
}
}
// MARK: - DataClass
struct DataClass: Codable {
let totalRecords: Int
let records: [Record]
enum CodingKeys: String, CodingKey {
case totalRecords = "TotalRecords"
case records = "Records"
}
}
// MARK: - Record
struct Record: Codable {
let id: Int
let title, shortDescription: String
let collectedValue, totalValue: Int
let startDate, endDate: String
let mainImageURL: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case title, shortDescription, collectedValue, totalValue, startDate, endDate, mainImageURL
}
}
Tip : Don't use NS stuff in swift and avoid using Data(contentsOf: as it blocks the main thread
There are multiple ways, one way to create a modelobject of
struct RecordItem : Codable {
var id : Int?
var collectedValue : Int?
var endDate : String?
var mainImageURL: String?
var shortDescription : String?
var title :String?
var startDate : String?
var totalValue : Int?
}
and
struct Records : Codable {
var items : [RecordItem]?
}
and use this. = let item = data [index]
print (item. collectedValue) and so on.
seconds methods, you already created dict, then extract all keys and array objects using ["Key": "Value"] and set to any variable.

Result of call to 'updateWeatherIcon(condition:)' is unused and therefore will not show UIImage

I am building a small Weather app that is accessing the openweathermap API. I am using JSONDecoder to parse the JSON from the API. For the most part, I am able to get most of the data in the simulator. Except for the UIImage that is supposed to appear on the screen. The image is in the image.xcassets. Below is the struct.
import UIKit
import CoreLocation
struct WeatherData: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Int
let sys: Sys
let id: Int
let name: String
let cod: Int
}
struct Clouds: Codable {
let all: Int
}
struct Coord: Codable {
let lon, lat: Double
}
struct Main: Codable {
let temp: Double
let pressure, humidity: Int
let tempMin, tempMax: Double
// enum CodingKeys: String, CodingKey {
// case temp, pressure, humidity
// case tempMin = "temp_min"
/ / case tempMax = "temp_max"
// }
}
struct Sys: Codable {
let type, id: Int
let message: Double
let country: String
let sunrise, sunset: Int
}
struct Weather: Codable {
let id: Int
let main, description, icon: String
}
struct Wind: Codable {
let speed: Double
let deg: Int
}
The code that accesses that passes the JSON is below:
private func getWeatherData(parameters: [String : String]) {
guard let lat = parameters["lat"],
let long = parameters["long"],
let appID = parameters["appid"] else { print("Invalid parameters"); return }
var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")!
let queryItems = [URLQueryItem(name: "lat", value: lat),
URLQueryItem(name: "lon", value: long),
URLQueryItem(name: "appid", value: appID)]
urlComponents.queryItems = queryItems
guard let url = urlComponents.url else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.async { // never, never, never sync !!
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
//self.updateWeatherData(city)
self.weatherData.description = city.weather[0].main
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
WeatherDataModel().updateWeatherIcon(condition: self.weatherData.condition)
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
and the function that show the data on the simulator is:
func updateUIWeatherData() {
cityLabel.text = weatherData.city
temperatureLabel.text = String(weatherData.temperature)
decriptionLabel.text = String(weatherData.description)
weatherIcon.image = UIImage(named: weatherData.weatherIconName)
}
I have looked at other example of this waring and not really sure what this means in reference to this app.
Example of result of call is unused.
Any help would be appreciated.
It seems to me like you want the line
self.weatherData.weatherIconName = WeatherDataModel().updateWeatherIcon(condition: self.weatherData.condition)
instead of
WeatherDataModel().updateWeatherIcon(condition: self.weatherData.condition)
The warning is saying you're calculating the weatherIcon, but it's not being assigned to anything (your weatherData variable)

How to organise JSON Structs in an array

I have used JSON structs before and managed to get them working with a different API, but this API's JSON data is slightly different, it seems to encompass the data in an array called 'List'. I assume it is the Structs below that are in the incorrect format? As when I run the app, I don't get any error messages, but the Label value that I am trying to change, does not change, nor does the value of 'Test' get printed to the console. I am trying to call the Description value and print it to a label.
JSON Structs below:
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : Cityy
let coordinate : Coordi
}
struct Coordi : Decodable {
let lat, lon : Double
}
struct Cityy : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Mainn : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Windd : Decodable {
let speed : Double
let deg : Double
}
struct Weatherr : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weatherr]
let clouds : Cloudss
let wind : Windd
let sys : Syss
let dtTxt : String
let rain: Rainn?
let city: Cityy
}
struct Syss : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double?
}
struct Cloudss : Decodable {
let all : Int
}
struct Rainn: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d = "d"
case n = "n"
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
ViewController below:
class ForecastViewController: UIViewController {
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard let APIUrl = URL (string: "https://api.openweathermap.org/data/2.5/forecast?q=London&APPID=***APIKEY***&units=metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoderr = JSONDecoder()
do {
decoderr.keyDecodingStrategy = .convertFromSnakeCase
decoderr.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoderr.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.city.name { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
}
else
{
print("weather not found")
}
} catch {
print(error.localizedDescription)
}
}.resume()
Your structs are were wrong before you edited the question.
The 5 day / 3 hour Forecast API of openweathermap.org sends a different JSON structure as the Current Weather Data.
You can create the structs very easy yourself:
Download the Data
Create a (JSON) string from the data
Copy the text
Open app.quicktype.io
Paste the text in the JSON text field on the left side
Add a suitable name for the root object.
quicktype.io creates the structs for you.
The forecast structs are (except Rain there are no optionals at all)
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : City
}
struct Coordinate : Decodable {
let lat, lon : Double
}
struct City : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Wind : Decodable {
let speed : Double
let deg : Double
}
struct Weather : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weather]
let clouds : Clouds
let wind : Wind
let sys : Sys
let dtTxt : String
let rain: Rain?
}
struct Sys : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double
}
struct Clouds : Decodable {
let all : Int
}
struct Rain: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d, n
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
To decode the structs you have to add date and key decoding strategies.
List and Weather are arrays
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoder.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.weather.first? { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
} else { print("weather not found") }

How to create a function to show reviews in a view of my application (Swift-JSON)

I'm following https://developers.google.com/places/web-service/details this doc to add all the information about a place, and for example to add "geometry"
"geometry" : {
"location" : {
"lat" : -33.866651,
"lng" : 151.195827
},
i created this function in my class that work well
private let geometryKey = "geometry"
private let locationKey = "location"
private let latitudeKey = "lat"
private let longitudeKey = "lng"
class EClass: NSObject {
var location: CLLocationCoordinate2D?
init(placeInfo:[String: Any]) {
placeId = placeInfo["place_id"] as! String
// coordinates
if let g = placeInfo[geometryKey] as? [String:Any] {
if let l = g[locationKey] as? [String:Double] {
if let lat = l[latitudeKey], let lng = l[longitudeKey] {
location = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
}
}
}
}
but but i'm having difficulty adding "reviews"
"reviews" : [
{
"author_name" : "Robert Ardill",
"author_url" : "https://www.google.com/maps/contrib/106422854611155436041/reviews",
"language" : "en",
"profile_photo_url" : "https://lh3.googleusercontent.com/-T47KxWuAoJU/AAAAAAAAAAI/AAAAAAAAAZo/BDmyI12BZAs/s128-c0x00000000-cc-rp-mo-ba1/photo.jpg",
"rating" : 5,
"relative_time_description" : "a month ago",
"text" : "Awesome offices. Great facilities, location and views. Staff are great hosts",
"time" : 1491144016
}
],
i tried to follow the same concept of the function i created for geometry like this
if let t = place.details?["reviews"] as? [String:Any] {
if let n = t["author_name"], let m = t["text"] {
Mylabel.text = "\(t)"
}
but is not working, i also tried to add a breakpoint and only the first line enters. What can i do? How can i create a build to show the review with a label or anything i need?
Take advantage of Codable in Swift 4. You can simply convert your JSON into a specific struct. e.g. Based on your JSON:
let json = """
{
"reviews" : [
{
"author_name" : "Robert Ardill",
"author_url" : "https://www.google.com/maps/contrib/106422854611155436041/reviews",
"language" : "en",
"profile_photo_url" : "https://lh3.googleusercontent.com/-T47KxWuAoJU/AAAAAAAAAAI/AAAAAAAAAZo/BDmyI12BZAs/s128-c0x00000000-cc-rp-mo-ba1/photo.jpg",
"rating" : 5,
"relative_time_description" : "a month ago",
"text" : "Awesome offices. Great facilities, location and views. Staff are great hosts",
"time" : 1491144016
}
]
}
"""
You can convert it into a Response struct using the following code:
struct Response: Codable {
struct Review: Codable, CustomStringConvertible {
let text: String
let authorName: String
var description: String {
return "Review text: \(text) authorName: \(authorName)"
}
enum CodingKeys: String, CodingKey {
case text
case authorName = "author_name"
}
}
let reviews: [Review]
}
do {
if let data = json.data(using: .utf8) {
let decoder = JSONDecoder()
let decoded = try decoder.decode(Response.self, from: data)
print(decoded.reviews)
} else {
print("data is not available")
}
} catch (let e) {
print(e)
}
In your code t is not a Dictionary it is an Array instead. So try doing something like this. Rest of that you can change as per your logic.
if let t = place.details?["reviews"] as? [String:Any] {
for dic in t {
if let n = dic["author_name"], let m = dic["text"] {
Mylabel.text = "\(t)"
}
}
}
Yeah, but You can also try to make it like that:
struct reviews: Codable{
var reviews: [review]?
}
struct review: Codable{
var author_name: String?
var author_url: String?
var language: String?
var profile_photo_url: String?
var rating: Int?
var relative_time_description: String?
var text: String?
var time: Int?
}
And then:
if let dict = place.details?["reviews"] as? [String: Any],
let dataToDecode = dict.data(using: .utf8){
do{
let decodedReviews = try JSONDecoder().decode(reviews.self, from: dataToDecode)
// here you have decoded reviews in array
}catch let err{
print(err)
}
}

Resources