Swift - View loads before http request is finished in viewDidLoad() - ios

I am trying to load a values from a database and put them into a UITableView in the viewDidLoad function in one of my Swift files. When debugging, at the time of the view rendering, the list of values is empty, but after the view loads, the list gets populated by the view loads. I don't have much experience with threads in Swift, so I am not exactly sure why this is happening, any ideas? I have tried to run DispatchQueue.main.async, but that did not work My code is below:
override func viewDidLoad() {
super.viewDidLoad()
// Load any saved meals, otherwise load sample data.
loadDbMeals()
}
private func loadDbMeals() {
var dbMeals = [Meal]()
let requestURL = NSURL(string: self.URL_GET)
let request = NSMutableURLRequest(url: requestURL! as URL)
request.httpMethod = "GET"
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//parsing the response
do {
//converting response to NSDictionary
let myJSON = try JSONSerialization.jsonObject(with: data!, options: [.mutableContainers]) as? NSDictionary
//parsing the json
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
}
}
} catch {
print(error)
}
}
meals += dbMeals
//executing the task
task.resume()
}
So, the current order of breakpoints, is the call to loadDbMeals() in the viewDidLoad() function, then it tries to add the dbMeals variables to the global meals variable, and then the http request gets executed, after the empty list has already been added. I appreciate any help!

Reload your table after loading data
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
DispatchQueue.main.async{
self.tableView.reloadData()
}
}
}

the request happens asynchronously. so the view is loaded while the request may still be in progress.
move the meals += dbMeals line into the request's completion handler (after the for loop), add a self. to the meals var since you are referencing it from within a closure and reload the tableview from the main thread afterwards:
DispatchQueue.main.async{
self.tableView.reloadData()
}

Because dataTask is not a synchronised call, we need to use lock to wait until all fetch is finished.
Code will look something like this:
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
let lock = DispatchSemaphore(value: 0)
// Load any saved meals, otherwise load sample data.
self.loadDbMeals(completion: {
lock.signal()
})
lock.wait()
// finished fetching data
}
}
private func loadDbMeals(completion: (() -> Void)?) {
var dbMeals = [Meal]()
let requestURL = NSURL(string: self.URL_GET)
let request = NSMutableURLRequest(url: requestURL! as URL)
request.httpMethod = "GET"
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//parsing the response
do {
//converting response to NSDictionary
let myJSON = try JSONSerialization.jsonObject(with: data!, options: [.mutableContainers]) as? NSDictionary
//parsing the json
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
}
}
} catch {
print(error)
}
// call completion
completion()
}
meals += dbMeals
//executing the task
task.resume()
}
So execute loadDbMeals with completion block which will be called when fetching is finished and lock will wait until completion block is called.

Related

Json data to label

I have been struggling with this for a while and just need some quick help with this basic question.
let url = URL(string: "https://api.coindesk.com/v1/bpi/currentprice.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("Error!")
}
else
{
if let content = data
{
do
{
// Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let rates = myJson["bpi"] as? NSDictionary
{
if let currency = rates["USD"] as NSDictionary
{
if let money = currency["rate"]
{
print(money)
}
}
}
}
catch
{
}
}
}
}
task.resume()
self.label.text = (self.money as String?)
I am trying to pass the numerical value for "money" to a label in my view controller.
The main issue is that you are trying to update the label from outside the completion handler of your asynchronous network request. However, there are several other issues with your code. You shouldn't use NSDictionary in Swift, use [String:Any] when parsing JSON dictionaries. .mutableContainers also doesn't have any effect in Swift. Also make sure that you dispatch any UI related updates to the main thread.
let url = URL(string: "https://api.coindesk.com/v1/bpi/currentprice.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else {
print(error!); return
}
do {
if let myJson = try JSONSerialization.jsonObject(with: content) as? [String:Any],
let rates = myJson["bpi"] as? [String:Any],
let currency = rates["USD"] as? [String:Any],
let money = currency["rate"] as? String {
DispatchQueue.main.async{
self.label.text = money
}
}
} catch{
print(error)
}
}
task.resume()

Table only shows when interacting and not by default

I have a table in a view controller that is populated through a dictionary from which information is retrieved via a JSON request. In the viewDidLoad() function, I call the function that retrieves the data which is added to `IncompletedDeadlines dictionary:
override func viewDidLoad() {
super.viewDidLoad()
self.IncompleteDeadlines = [String:AnyObject]()
self.retrieveIncompletedDeadlines()
}
Everything works however the table only shows when interacted with. I thought maybe the best way to show the table the moment the view appears is by adding a tableView.reload to viewDidAppear as so:
override func viewDidAppear(_ animated: Bool) {
self.tableView.reloadData()
}
But this doesn't fix it. I have attached pictures for clarity of the situation. Picture one shows the view the moment the view appears. Picture 2 only happens once the table is interacted with i.e. swiped. So my question is how can I get the table to show immediately? I understand there can be a delay because of the load, but I shouldn't have to interact with it for it to show:
When the view is interacted with i.e. swiped:
The retrieveIncompletedDeadlines() function is as so:
func retrieveIncompletedDeadlines(){
let myUrl = NSURL(string: "https://www.example.com/scripts/retrieveIncompleteDeadlines.php");
let request = NSMutableURLRequest(url:myUrl! as URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("error=\(String(describing: error))")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let checker:String = parseJSON["status"] as! String;
if(checker == "Success"){
let resultValue = parseJSON["deadlines"] as! [String:AnyObject]
self.IncompleteDeadlines = resultValue
}
self.tableView.reloadData()
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
self.tableView.reloadData()
}
JSON will be parsed on the background thread but any update to the UI must be done on the main thread hence you have to do it inside DispatchQueue.main.async {} This article explains well what is the problem.
Furthermore I would write a completions handler which returns the data once the operation has finished. This is another interesting article about.
Completion handlers are super convenient when your app is doing something that might take a little while, like making an API call, and you need to do something when that task is done, like updating the UI to show the data.
var incompleteDeadlines = [String:AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
//please note your original function has changed
self.retrieveIncompletedDeadlines { (result, success) in
if success {
// once all the data has been parsed you assigned the result to self.incompleteDeadlines
self.incompleteDeadlines = result
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func retrieveIncompletedDeadlines(_ completion:#escaping ([String:AnyObject] , _ success: Bool)-> Void){
let myUrl = NSURL(string: "https://www.example.com/scripts/retrieveIncompleteDeadlines.php");
let request = NSMutableURLRequest(url:myUrl! as URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("error=\(String(describing: error))")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let checker:String = parseJSON["status"] as! String;
var resultValue = [String:AnyObject]()
if(checker == "Success"){
resultValue = parseJSON["deadlines"] as! [String:AnyObject]
}
completion(resultValue, true)
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
}

Json and variable scope

My error should be quite obvious but I can't find it;
I've a global variable initialized a the beginning of my class:
class InscriptionStageViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var lesSemaines = [String]()
I try to populate this array with a distant json file using that function
func getSemainesStages(){
let url = URL(string: "http://www.boisdelacambre.be/ios/json/semaines.json")
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
let listeSemaines = myJson["semaine"] as! [[String:AnyObject]]
//print(listeSemaines)
for i in 0...listeSemaines.count-1 {
var tabSem = listeSemaines[i]
let intituleSemaine:String = tabSem["intitule"] as! String
//let dateSemaine:String = tabSem["date"] as! String
DispatchQueue.main.sync
{
self.lesSemaines.append(intituleSemaine)
}
}
} catch
{
print("erreur Json")
}
}
}
task.resume()
}
When I call my function in the viewDidLoad and then I print my global array, it's empty (the URL is correct, the json data is read correctly and when I read the data appended in the array in the loop, it print the (so) needed value...)
Thanks in advance
The download takes time. Introduce another methode:
func updateUi() {
print(lesSemaines)
//pickerView.reloadAllComponents()
}
And call it after the download finished:
func getSemainesStages(){
// ... your code
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
// ... your code
for tabSem in listeSemaines{
guard let intituleSemaine = tabSem["intitule"] as? String else {
print("erreur Json")
continue
}
self.lesSemaines.append(intituleSemaine)
}
// update UI *after* for loop
DispatchQueue.main.async {
updateUi()
}
// ... your code
}
}
I have updated your code to Swift 3. Please replace it with below code.
func getSemainesStages(){
let url = URL(string: "http://www.boisdelacambre.be/ios/json/semaines.json")
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String: Any]
let listeSemaines = myJson["semaine"] as! [[String: Any]]
for i in 0...listeSemaines.count-1 {
var tabSem = listeSemaines[i]
let intituleSemaine:String = tabSem["intitule"] as! String
self.lesSemaines.append(intituleSemaine)
}
print(self.lesSemaines)
} catch {
print("erreur Json")
}
}
}
task.resume()
}

swift what is the timing to reload data after NSURLSession POST request

I am trying to show some data in tableView after getting JSON. However, it is failed by using tableView.reloadData() . I menu delay.
My situation is that the tableView will reload data after 14-20 seconds of getting JSON, but it will reload data immediately when user active the table.
Swift:
func getJson(word: String){
let url: NSURL = NSURL(string: "MyPHP.php")!
let session = NSURLSession.sharedSession()
let request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let bodydata = "label=\(word)"
request.HTTPBody = bodydata.dataUsingEncoding(NSUTF8StringEncoding)
if bodydata != " " {
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if(statusCode == 200) {
self.libraryArray.removeAll()
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
print(json)
if let book = json["Book"] as? [[String: AnyObject]]{
for books in book {
if let BookName = book["Name"] as? [String: AnyObject] {
let name = BookName["name"] as? String
let author = BookName["author"] as? String
let bookDict = [
"name": name,
"id": id,
"author": author]
self.libraryArray.append(bookDict)
self.tableView.reloadData() // has 14- 20 seconds delay, but the JSON has already received
}
}
}
}catch {
print("error:\(error)")
}
}
}
task.resume()
// self.tableView.reloadData() if i try to reload here, it will not be executed.
}
}
#IBAction func Next(sender: AnyObject) {
self.getJson("someString")
//self.tableView.reloadData() if i try to reload here, it will be fail.
}
what is the problem of the delay? How to solve it?
What is the right timing to reload tableView?
Replace your self.tableView.reloadData() with this
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
You are asking the table to reload while you didn't finish parsing the json. you do that after every Book parsing. you should do it after you finish parsing. the only explanation of the delay is that you have a huge data that is taking all that time for parsing!! Is that possible?
func getJson(word: String){
let url: NSURL = NSURL(string: "MyPHP.php")!
let session = NSURLSession.sharedSession()
let request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let bodydata = "label=\(word)"
request.HTTPBody = bodydata.dataUsingEncoding(NSUTF8StringEncoding)
if bodydata != " " {
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if(statusCode == 200) {
self.libraryArray.removeAll()
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
print(json)
if let book = json["Book"] as? [[String: AnyObject]]{
for books in book {
if let BookName = book["Name"] as? [String: AnyObject] {
let name = BookName["name"] as? String
let author = BookName["author"] as? String
let bookDict = [
"name": name,
"id": id,
"author": author]
self.libraryArray.append(bookDict)
self.tableView.reloadData() // has 14- 20 seconds delay, but the JSON has already received
}
}
}
}catch {
print("error:\(error)")
}
self.tableView.reloadData() // <<----- HERE
}
}
task.resume()
// self.tableView.reloadData() if i try to reload here, it will not be executed.
}
}
#IBAction func Next(sender: AnyObject) {
self.getJson("someString")
//self.tableView.reloadData() if i try to reload here, it will be fail.
}

Show JSON data in a TableView with Swift

I am trying to display some data I take from a data base into a TableView but the data is not shown in the TableView. The data I receive is formatted in JSON.
This is the data I receive form the data base and what I want to print in the TableViewis just David:
{"name":"David"}
This is the code to get the data from de data base:
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://localhost:8888/Patients.php")!)
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError {
print("Could not complete the request \(error)")
}
else {
do {
self.json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), {
if let parseJSON = self.json{
let name = parseJSON["name"] as! String
self.arrayData.append(name)
print("Data1:\(self.arrayData)")
}
})
}
}
task.resume()
arrayData is an array where I put the data I receive from the data base and it is declared like this:
var arrayData: [String] = []
The code
print("Data1:\(self.arrayData)")
show in the console this Data1:["David"], so I get the data correctly.
Then I implement the methods to print in the ´TableView´, the numberOfSectionsInTableViewmethod, the numberOfRowsInSection method and the cellForRowAtIndexPath method but the data is not printed in the TableView.
I think the problem is that the TableViewis drawn before I put the data in the array so it prints nothing because the array is empty, and I don´t know how to fix it.
Anyone knows what is my problem?
yes, you're right.
session.dataTaskWithRequest
is async. Data is not returned immediately, it have delay.
You must to reload tableview after recieved data:
self.arrayData.append(name)
self.tableview.reloadData()
Usually i will use block:
func getData(handleComplete:((arr:NSMutableArray)->())){
let aray = NSMutableArray()
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://localhost:8888/Patients.php")!)
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError {
print("Could not complete the request \(error)")
}
else {
do {
self.json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), {
if let parseJSON = self.json{
let name = parseJSON["name"] as! String
aray.append(name)
print("Data1:\(self.arrayData)")
}
handleComplete(aray)
})
}
}
task.resume()
arrayData
}
and in viewdidload i will call:
override func viewDidLoad() {
super.viewDidLoad()
self.getData { (arr) -> () in
self.tableview.reloadData()
}
}
it 's better

Resources