I know this is classic concurrency problem, but I'm new to Swift and I feel like I've read through every relevant blog post about this. I know I'm fetching data correctly because it's showing up in my console, but I can't get it to display in my simulator. I believe it's because I'm displaying the UI before the data has loaded.
How can I display the data after I 100% fetched all the data. I'll add the relevant code and screenshots of the console. Sorry if this is a "basic" question.
Structs
struct Welcome: Decodable, Identifiable {
var id: Int?
var readings: [Reading]?
var include_maintenance_recommendations: Bool?
var pool_volume: Int?
var created_at, updated_at: String?
}
struct Reading: Decodable {
var analyte: String?
var value: String?
}
UI
var results = Welcome()
struct ContentView: View {
#State var res = Welcome()
var body: some View {
Text(results.created_at ?? "N/A")
.onAppear(){
fetchResults().getData {(res) in
self.res = res
}
}
}
}
Fetch Func
class fetchResults{
func getData(completion: #escaping (Welcome) -> ()){
print("Fetch")
let parameters = "{ \"pool_volume\": 10000, \"readings\": [ { \"analyte\": \"fc\", \"value\": 1.6 }, { \"analyte\": \"ph\", \"value\": 7.3 } ]}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://www.biolabhydra.com/api/v3/water_tests")!,timeoutInterval: Double.infinity)
request.addValue("Basic ***********************", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("csrftoken=***********************", forHTTPHeaderField: "Cookie")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { (data, _, _) in
let resultList = try! JSONDecoder().decode(Welcome.self, from: data!)
print(" ")
print("ID: \(resultList.id ?? 111)")
print("VOLUME: \(resultList.pool_volume ?? 111)")
print("RECOMMENDATIONS: \(resultList.include_maintenance_recommendations ?? true)")
print("CREATD AT: \(resultList.created_at ?? "N/A")")
print("UPDATED AT: \(resultList.updated_at ?? "N/A")")
print("READINGS: \(String(describing: resultList.readings))")
print(" ")
print("SUCCESS: Got data - \(data! )")
}
task.resume()
}
}
Use local property instead of global variable
struct ContentView: View {
#State var res = Welcome()
var body: some View {
Text(res.created_at ?? "N/A") // << here !!
.onAppear(){
fetchResults().getData {(res) in
self.res = res
}
}
}
}
add callback after decoded data
let task = URLSession.shared.dataTask(with: request) { (data, _, _) in
let result = try! JSONDecoder().decode(Welcome.self, from: data!)
print(" ")
print("ID: \(resultList.id ?? 111)")
print("VOLUME: \(resultList.pool_volume ?? 111)")
print("RECOMMENDATIONS: \(resultList.include_maintenance_recommendations ?? true)")
print("CREATD AT: \(resultList.created_at ?? "N/A")")
print("UPDATED AT: \(resultList.updated_at ?? "N/A")")
print("READINGS: \(String(describing: resultList.readings))")
print(" ")
print("SUCCESS: Got data - \(data! )")
DispatchQueue.main.async {
completion(result) // << here !!
}
}
Related
in postman response structure like this:
{
"categories": [
{
"id": 48,
"name": "Round-The-Clock",
"description": "24 hours round the clock menu",
"status": "enabled",
"products": [
{
"id": 280,
"name": ".Tea",.....
for this i have created Decodable model like this
struct Categories: Codable {
let categories: [Category]?
let featuredProducts: [Product]?
//coding keys..
}
struct Category: Codable {
let id: Int?
let name, categoryDescription: String?
let products: [Product]?
}
struct Product: Codable {
let id: Int?
let name, productDescription: String?
}
Parsing code: with this code break point hits with this if let jsonData = try? decoder.decode(Categories.self, from: respData) line but not hitting print("the categories are: (jsonData)") line and nothing comes in console, why? where am i wrong.. how to get response
class FoodMenuViewController: UIViewController {
private var catData: Categories? {
didSet{ }
}
func foodMenuServicecall(){
let urlStr = "http://54.149.84.126/categories?shop=1"
let url = URL(string: urlStr)
var req = URLRequest(url: url!)
req.httpMethod = "GET"
req.addValue("X-Requested-With", forHTTPHeaderField: "Content-Type")
req.addValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let respData = data else {return}
guard error == nil else {
print("error")
return
}
do{
let decoder = JSONDecoder()
if let jsonData = try? decoder.decode(Categories.self, from: respData) {
print("the categories are: \(jsonData)")
self.catData = jsonData
}
}
catch {print("catch error")}
}).resume()
}
EDIT: if i test like this i am getting response but here
func foodMenuServicecall(){
if let url = URL(string: "http://54.149.84.126//categories?shop=1"){
var req = URLRequest(url: url)
req.allHTTPHeaderFields = ["X-Requested-With" : "XMLHttpRequest"]
URLSession.shared.dataTask(with: req) { data, _, err in
guard let safeData = data else{return}
print(String(data: safeData, encoding: .utf8) ?? "")
}.resume()
}
}
o/p in consol:
Your decodable model expects a key of "categoryDescription", your JSON has a key of "description".
I am trying to parse JSON using the following method, but XCode is giving me an error where I have declared "data" .
I am new, I don't understand what is wrong. Please help me.
import UIKit
struct Contacts: Decodable {
let id: Int
let name: String
let email: String
}
class ViewController: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
let urlString = "https://api.androidhive.info/contacts/"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
}
guard let data = data else {return}
//let datastring = String(data: data, encoding: .utf8)
do
{
let contact = try JSONDecoder().decode([Contacts].self, from: data)
print(contact.name)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
}
}
}
Three major issues.
You are ignoring the root object which is a dictionary containing the contacts array.
The value for key id is String, not Int.
A hard rule is : Everything in double quotes is String even "12" and "false"
You have to resume the task and put the code to parse the JSON into the completion handler.
struct Root : Decodable {
let contacts : [Contact]
}
struct Contact : Decodable { // It's recommended to name this kind of struct in singular form
let id, name, email: String
}
...
override func viewDidLoad()
{
super.viewDidLoad()
let urlString = "https://api.androidhive.info/contacts/"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode(Root.self, from: data!)
let contacts = result.contacts
for contact in contacts {
print(contact.name)
}
} catch {
print("Error deserializing json:", error)
}
}.resume()
}
//
// ViewController.swift
// PostMethodTest
//
// Created by HABIB UR REHMAN on 12/11/2018.
// Copyright © 2018 HABIB UR REHMAN. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
struct Resturant: Decodable {
var name: String
var deliveryCharges: String
var email: String
init(_ dictionary: [String: Any]) {
self.name = dictionary["name"] as? String ?? ""
self.deliveryCharges = dictionary["deliveryCharges"] as? String ?? ""
self.email = dictionary["email"] as? String ?? ""
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "your Link Here ") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
print(jsonResponse) //Response result
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
}
Please Try this its working for me.
func getContactListsApiCalling() {
var request = URLRequest(url: URL(string: "https://api.androidhive.info/contacts/")!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
if let Data = data {
let responseJSON = try JSONSerialization.jsonObject(with: Data) as? Dictionary<String, AnyObject> ?? [:]
if let contacts = responseJSON["contacts"] as? [[String :AnyObject]] {
for contact in contacts {
let id = contact["id"] as? String ?? ""
let name = contact["name"] as? String ?? ""
let email = contact["email"] as? String ?? ""
let address = contact["address"] as? String ?? ""
let gender = contact["gender"] as? String ?? ""
print(id,name,email,address,gender)
}
}
}
} catch {
print("error")
}
})
task.resume()
}
}
I am confusing to getting detail of fruit
{
"fruits": [
{
"id": "1",
"image": "https://cdn1.medicalnewstoday.com/content/images/headlines/271/271157/bananas.jpg",
"name": "Banana"
},
{
"id": "2",
"image": "http://soappotions.com/wp-content/uploads/2017/10/orange.jpg",
"title": "Orange"
}
]
}
Want to parse JSON using "Decodable"
struct Fruits: Decodable {
let Fruits: [fruit]
}
struct fruit: Decodable {
let id: Int?
let image: String?
let name: String?
}
let url = URL(string: "https://www.JSONData.com/fruits")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do{
let fruits = try JSONDecoder().decode(Fruits.self, from: data)
print(Fruits)
}catch {
print("Parse Error")
}
also can you please suggest me cocoapod library for fastly download images
The issue you are facing is because your JSON is returning different data for your Fruits.
For the 1st ID it returns a String called name, but in the 2nd it returns a String called title.
In addition when parsing the JSON the ID appears to be a String and not an Int.
Thus you have two optional values from your data.
As such your Decodable Structure should look something like this:
struct Response: Decodable {
let fruits: [Fruits]
}
struct Fruits: Decodable {
let id: String
let image: String
let name: String?
let title: String?
}
Since your URL doesn't seem to be valid, I created the JSON file in my main bundle and was able to parse it correctly like so:
/// Parses The JSON
func parseJSON(){
if let path = Bundle.main.path(forResource: "fruits", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONDecoder().decode(Response.self, from: data)
let fruitsArray = jsonResult.fruits
for fruit in fruitsArray{
print("""
ID = \(fruit.id)
Image = \(fruit.image)
""")
if let validName = fruit.name{
print("Name = \(validName)")
}
if let validTitle = fruit.title{
print("Title = \(validTitle)")
}
}
} catch {
print(error)
}
}
}
Hope it helps...
// Parse Json using decodable
// First in create Structure depends on json
//
//
//
struct Countory : Decodable {
let name: String
let capital: String
let region: String
}
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
var countories = try JSONDecoder().decode([Countory].self, from: data!)
for country in countories {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
}
} catch {
print(" not ")
}
}.resume()
Model sample:
public struct JsonData: Codable{
let data: [Data]?
let meta: MetaValue?
let linksData: LinksValue?
private enum CodingKeys: String, CodingKey{
case data
case meta
case linksData = "links"
}
}
enum BackendError: Error {
case urlError(reason: String)
case objectSerialization(reason: String)
}
struct APIServiceRequest {
static func serviceRequest<T>(reqURLString: String,
resultStruct: T.Type,
completionHandler:#escaping ((Any?, Error?) -> ())) where T : Decodable {
guard let url = URL(string: reqURLString) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let books = try decoder.decode(resultStruct, from: responseData)
completionHandler(books, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
}
To Access:
let apiService = APIServiceRequest()
var dataArray: [String: Any]? //global var
apiService.serviceRequest(reqURLString: endPoint, resultStruct: VariantsModel.self, completionHandler: {dataArray,Error in})
POST Method
func loginWS(endpoint: String, completionHandler: #escaping (Any?) -> Swift.Void) {
guard let sourceUrl = URL(string: endpoint) else { return }
let request = NSMutableURLRequest(url: sourceUrl)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue(vehiceHeader, forHTTPHeaderField: "X-Vehicle-Type")
request.addValue(contentHeader, forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data else { return }
do {
let responseData = try JSONDecoder().decode(JsonData.self, from: data)
print("response data:", responseData)
completionHandler(responseData)
} catch let err {
print("Err", err)
}
}.resume()
}
hello i try to decode Json:
{"result":[
{"ID":"80",
"time":"09:00:00",
"status":[
{"status":0,"kirpeja_id":"74","name":"Natalja","image":"natalija255.png","duration":"00:20:00"},
{"status":1,"kirpeja_id":"80","name":"Lina","image":"kazkas.png","duration":"00:30:00"},
{"status":0,"kirpeja_id":"82","name":"Rasa ","image":"IMG_20170906_171553.jpg","duration":"00:40:00"}
]},
{"ID":"81",
"time":"09:10:00",
"status":[
{"status":0,"kirpeja_id":"66","name":"Ilona","image":"ilona_new.jpg","duration":"00:30:00"},
{"status":0,"kirpeja_id":"74","name":"Natalja","image":"natalija255.png","duration":"00:20:00"},
{"status":0,"kirpeja_id":"80","name":"Lina","image":"kazkas.png","duration":"00:30:00"},
{"status":0,"kirpeja_id":"82","name":"Rasa ","image":"IMG_20170906_171553.jpg","duration":"00:40:00"}
]},
...
here my classes
class TimeStatusResult: Codable {
let result: [TimeStatus]
init (result:[TimeStatus]) {
self.result = result
}
}
class TimeStatus: Codable {
let ID:String?
let time: String?
let status: [Status]
init (status:[Status]) {
self.ID = ""
self.time = ""
self.status = status
}
}
class Status: Codable {
let status: String?
let kirpeja_id: String?
let name: String?
let image: String?
let duration: String?
init () {
self.status = ""
self.kirpeja_id = ""
self.name = ""
self.image = "nophoto.jpg"
self.duration = ""
}
}
here my json function
final let jsonUrl = URL(string: "http://**********/getlaikas_new.php")
private var timeStatusResult = [TimeStatus]()
func downloadJson () {
guard let downloadURL = jsonUrl else {return}
var request = URLRequest(url: downloadURL)
request.setValue("application/x-www-form-urlencoded",forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "data=\(pInfo.paslaugosData!)&salonId=\(pInfo.ID!)&paslaugos_id=\(pInfo.paslaugosId!)"
request.httpBody = postString.data(using: .utf8, allowLossyConversion: true)
URLSession.shared.dataTask(with: request) {data, urlResponse, error in
guard let data = data , error == nil, urlResponse != nil else {
print ("something wrong")
return }
print ("downloaded!")
do
{
let decoder = JSONDecoder()
print (data)
let downloadedTimeStatus = try decoder.decode(TimeStatusResult.self, from: data)
self.timeStatusResult = downloadedTimeStatus.result
DispatchQueue.main.async {
// self.kirpejosPaslaugosTable.reloadData()
}
} catch {
print ("something wrong after download")
}
}.resume()
}
in this line i have issue
let downloadedTimeStatus = try decoder.decode(TimeStatusResult.self, from: data)
somebody can help me? :(
The error is very clear:
...burzua_1.Status.(CodingKeys in _479ABD1AF7892C9F2FD23EC23214E088).status], debugDescription: "Expected to decode String but found a number instead."
The value for key status is not in double quotes so it's an Int
class Status: Codable {
let status: Int
...
Please don't declare all properties carelessly as optional. For example status as well as all other keys is present in all Status dictionaries.
Guys I need assistance on swift 4.0
i have a simple function that calls a json and fills those vars with values.
self.getDataFromServer()
after 'self.getDataFromServer()' this will fill in the arrays with retrieved data. This was working great on the previous version of swift (swift 3).
This is the code after the self.getDataFromServer() and getting index out of range, because data isn't populated (PS: This was working on swift 3.0)
var totalAmount = [String]()
var remainingAmount = [String]()
self.getDataFromServer()
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
I'm getting error on 'newTotal' saying index out of range. Please Help.
I noticed that on swift 4, I'm facing this issue whenever i'm calling JSON.
The JSON Function is as below:
func getDataFromServer() {
let dateOfToday = Date()
let strDateOfToday = convertToString(myDate: dateOfToday)
let postString = "ANYTHING HERE";
let myUrl = URL(string: "ANYTHING HERE")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8);
//Start the task
let task = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
let values = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
let success = ((values.value(forKey: "success") as? [String])! as NSArray) as! [String]
if (success[0] == "1")
{
self.totalAmount = ((values.value(forKey: "totalAmount") as? [String])! as NSArray) as! [String]
self.remainingAmount = ((values.value(forKey: "remainingAmount") as? [String])! as NSArray) as! [String]
}
}
//Let's convert response sent from a server side script to a NSDictionary object:
task.resume()
}
Here is your perfect working solution using Closure
Swift 4
self.getDataFromServer { (arrTotalAmount, arrRemainingAmount) in
if arrTotalAmount.count > 0, arrRemainingAmount.count > 0 {
// Your code here
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
}
}
func getDataFromServer(completion: #escaping (_ arrTotalAmount: [String], _ arrRemainingAmount: [String]) -> Void) {
let dateOfToday = Date()
let strDateOfToday = convertToString(myDate: dateOfToday)
let postString = "ANYTHING HERE";
let myUrl = URL(string: "ANYTHING HERE")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8);
//Start the task
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
let values = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
let success = ((values.value(forKey: "success") as? [String])! as NSArray) as! [String]
if (success[0] == "1")
{
let totalAmount = ((values.value(forKey: "totalAmount") as? [String])! as NSArray) as! [String]
let remaininAmount = ((values.value(forKey: "remainingAmount") as? [String])! as NSArray) as! [String]
completion(totalAmount, remaininAmount)
}
}
//Let's convert response sent from a server side script to a NSDictionary object:
task.resume()
}
I can't really imagine this working in Swift 3, since the effects of your getDataFromServer function are asynchronous. More precisely, the function returns before the code inside the dataTask is executed.
Try something like this to execute code on the caller side after the dataTask has succeeded:
func getDataFromServer(onDone: () -> Void) {
...
let task = URLSession.shared.dataTask(with: request) { ... in
...
onDone()
}
task.resume()
}
And to call it:
var totalAmount = [String]()
var remainingAmount = [String]()
self.getDataFromServer {
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
}