Loading value in UITableViewController from async task - ios

I have a problem trying to load a UITableViewController.
I have an async task that is called in the loadView method. This async task works well and results are returned as I expect.
The problem is, the app fail when trying to populate the TableView.
I suspect that it's due to the fact that my data are not completely loaded when the method are called.
Is there anyway to force the TableView to wait on my async task to be finished ?
The function that loads my data:
func loadNetInformations(){
var postString: NSString = "info=all"
HTTPGet("myurl", "POST", postString){
(data: NSArray, error:String?)-> Void in
if error != nil{
println(error)
}
else{
for value in data{
var sService: NSString = "some value"
var aContent: NSArray = value["Content"] as NSArray
var sNumberCount: NSNumber = aContent.count
self.aListeService.addObject(sService)
self.aSizeOfDpt.addObject(sNumberCount)
self.aContenuListes.addObject(aContent)
self.bFinishLoading = true
} // End for in HTTPGet
} // End else in HTTPGet
} // End HTTPGet
} // End LoadNet Information
My HTTPGet method is as following:
func HTTPSendRequest(request: NSMutableURLRequest,
callback: (NSArray, String?) -> Void){
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
callback (NSArray(), error.localizedDescription)
println("error=\(error)")
return
}
else{
callback(NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)! as NSArray, nil)
}
}
task.resume()
}
func HTTPGet(url: String, requestMethod: String, postString: String, callback: (NSArray, String?) -> Void){
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = requestMethod
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
HTTPSendRequest(request, callback)
}
EDIT: Ok now I don't have the error anymore (I used a NSString as an NSInteger...) but it doesn't show anything
EDIT2:
here's my JSON format in case it can help:
[
{
"CodeAbsence" : "6000",
"Content" :
[
{
"Init":"DAS",
"Nom":"Name",
"Prenom":"Firstname",
"IdAbsence":"619",
"TimbreusePresent":"O",
"TimbreuseHeure":"14:44",
"TimbreuseRaison":"0",
"TimbreuseDate":"",
"CodeAbsence":"6000",
"Telephone":"248"
},
....
]
},
.......
]

You need to reload the tableView to trigger a table update and you need to trigger it on the main UI thread.
for value in data{
var sService: NSString = "some value"
var aContent: NSArray = value["Content"] as NSArray
var sNumberCount: NSNumber = aContent.count
self.aListeService.addObject(sService)
self.aSizeOfDpt.addObject(sNumberCount)
self.aContenuListes.addObject(aContent)
self.bFinishLoading = true
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
When you perform an asynchronous operation on a background thread your callback is still going to run on the background thread, so always be sure to switch back to the main thread before performing any UI task.

I finally found what was the problem. It was coming from the use of viewDidLoad() instead of viewDidAppear().

Related

Sending string from JSON data to variable outside of the function

I am attempting to take a string from JSON data and set it to a variable. My problem is that the variable shows as empty. I am using JSONDecoder to retrieve the JSON data and setting the string to a variable outside of the function. I then want to use that variable inside of another function
When I print the variable it still shows up as blank even after the function has loaded. Within the function the string appears correctly.
Code:
var filmTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) //Prints as an empty string
}
func loadFilms() {
let id = filmId
let apiKey = "97a0d64910120cbeae9df9cb675ad235"
let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)?api_key=\(apiKey)&language=en-US")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try! JSONDecoder().decode(Details.self, from: data)
self.filmTitle = details.title
print(self.filmTitle) //string prints correctly
}
}
})
task.resume()
}
What am I missing to correctly set the string to the variable?
Loading data from the internet is an asynchronous method. The print statement is being called before loadFilms() has completed.
Use a callback to get the data after it has completed.
func loadFilms(completion: #escaping (Details?, Error?) -> Void) {
//...
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try JSONDecoder().decode(Details.self, from: data)
completion(details, nil)
} catch {
completion(nil, error)
}
})
}
At the call site:
override func viewDidLoad() {
loadFilms { details, error in
if error { //* Handle Error */ }
self.filmTitle = details.title
print(filmTitle)
}
}
Web request are asynchronous and from the CP's perspective, take a long time to complete. When you call this:
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) // loadFilms() hasn't finished so `filmTitle` is empty
}
It's better to set a property observer on filmTitle:
var filmTitle: String? = nil {
didSet {
print(filmTitle)
Dispatch.main.async {
// update your GUI
}
}
}
The solution to this problem was to reload the collection view that the array was being sent to within the decoder function after the data was set to the array.

Calling methods in swift (Method doesnt finish before next line)

Trying to pull in some JSON data from an API and then save that to core data.
My current method of doing this is to pull in the JSON data and return that array which ill then iterate and save to core data.
Pull in Data: (Works fine)
func getPlayerDataFromAPI() -> [Dictionary<String,AnyObject>]{
let url: String = "http://api.fantasy.nfl.com/v1/players/stats?"
let request : NSMutableURLRequest = NSMutableURLRequest()
var jsonData = [Dictionary<String,AnyObject>]()
request.HTTPMethod = "GET"
request.URL = NSURL(string: url)
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
if (jsonResult != nil) {
if let playerData = jsonResult?["players"] as? [Dictionary<String, AnyObject>]{
jsonData = playerData
print(jsonData.count)
}
} else {
print("No Data")
}
}
catch {
print("Error Occured")
}
}.resume()
return jsonData;
}
And then I wanted to test the returned Dictionary to ensure it was being populated:
func saveData(){
let players = getPlayerDataFromAPI()
print(players.count)
}
I call saveData() in the viewController viewDidLoad method and get an empty dictionary... Moments later, the print statement in the JSON function prints.
0
1427
Is there a reason the getPlayerDataFromAPI() function doesnt finish before the print(count) is being called? Do I have this wrong logically? I always get an empty dictionary returned in this instance and thats no good.
You're trying to synchronously return the results of an asynchronous function. session.dataTaskWithRequest is passed a closure, which doesn't execute until the request completes. So your jsonData = playerData statement doesn't get executed until after your getPlayerDataFromAPI() function has already returned (at which point jsonData is still the empty dictionary you defined at the beginning of the function).
One way to do what you're trying to do is to allow a closure to be passed in to your function; something like this (I haven't tested this code):
func getPlayerDataFromAPI(completion: (data: [String: AnyObject]) -> Void)
Then, at the point you assign jsonData = playerData, you can "return" the data to the caller like this:
completion(data: jsonData)
Calling this function would look something like this:
getPlayerDataFromAPI() { (data) -> Void in
print(data)
}

Making a re-useable function of JSON URL fetching function in SWIFT 2.0

I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}

Need to return json obj so I display in list view getting back nil

I am very new to swift. I am trying to return back the JSON and view it in a list view, I cant get the JSON from my AppApi class to return back to my viewDidLoad(). Any help would be appreciated.
Thank you in advanced.
Teli
override func viewDidLoad() {
super.viewDidLoad()
let api = AppAPI(token:self.toPassToken)
var test = api.getOrders()
println("why does test come back as an empty array")
println(test)
println(test.count)
}
class AppAPI {
var token: String
let apiEndPoint = "endpoint"
let apiUrl:String!
let consumerKey:String!
let consumerSecret:String!
var returnData = [:]
init(token:String){
self.apiUrl = “hidden-for-security”
self.consumerKey = "token"
self.consumerSecret = "my consumer secret"
self.token = token
}
func getOrders() -> [JSON] {
return makeCall("contacts")
}
func makeCall(section:String) -> [JSON] {
let params = ["token":"\(self.token)"]
Alamofire.request(.POST, "\(self.apiUrl)", parameters: params)
.responseJSON { (request, response, json, error) -> Void in
println("error \(request)")
self.returnData = json! as! NSDictionary
}
return results!
}
}
In your makeCall(section:String) -> [JSON] function you are returning results!. Where is results ever set in this function?
Did you mean to return returnData instead?
Alamofire.request performs an asynchronous request; responseJSON will execute that closure when the request completes at some point in the future.
You almost certainly do not want to block your viewDidLoad method until this request finishes as that would block your main thread and leave the app unresponsive. Instead start the request in viewDidLoad and react whenever the request finishes.
One way you might do this is by passing a closure to getOrders which you could execute when the request finishes.

how to modify data in an asynchronous URLrequest in swift before reaching the return statement

I've started learning ios development a while back and I've reached the part where I'm trying to send and receive data from a server.
I've ran into an issue where for example if I wanted to sign a user in or sign him up using an Asynchronous connection. Code example on a datamanager class using swiftyjson :
class func signUp() -> NSString {
var url: NSURL = NSURL(string: "http://localhost/Test/signup.php")!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
var bodyData = "username=Datforis&name=firas&password=123123"
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
var status = "error"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
println(response)
if(error != nil){
//handleerror
}
else if data == nil {
println("Server could not be reached please check your internet connection")
status = "connectionerror"
}
else {
println("i'm here")
let json = JSON(data : data)
var msgint : Int = json["status"].int!
status = String(msgint)
println(status)
}
}
println(status + " is current" )
return status //returns status if sign up was successful or not
}
the code will always return "error" (default value) because the return statement is being executed before the status string is being modified.
If I repeat the same code but with a synchronous connection it works fine, but everyone I ask tells me to steer clear of Synchronous connections because they freeze the UI while they execute
Is there a way to handle this without having to use a Synchronous connection? Or is it the only way? And is there a better way to handle a sign up/sign in request or any request in general?
Thanks
I would advice you to take a look at this article about Completion handlers.
Completion handlers are used instead of return statements.
The Completion handler will be called when your statement is completed instead of when it reaches the end of the function.
The link I mentioned before has the following example
func hardProcessingWithString(input: String, completion: (result: String) -> Void) {
…
completion(“we finished!”)
}
This could be used in mathematical calculations which take a long time, or in your case, URL requests. To retrieve the data you could use
hardProcessingWithString(“commands”) {
(result: String) in
println(“got back: (result)“)
}
thanks to the selected answer I corrected my code
Placed in the Datamanager :
class func signUp(username : String , completion: (status : NSString)->Void) {
var url: NSURL = NSURL(string: "http://localhost/WhosIn/signup.php")!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
var bodyData = "username=Datforis&name=firas&password=123"
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
var status = "error"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
println(response)
if(error != nil){
completion(status: status)
}
else if data == nil {
println("Server could not be reached please check your internet connection")
status = "connectionerror"
completion(status: status)
}
else {
println("i'm here")
let json = JSON(data : data)
var msgint : Int = json["status"].int!
status = String(msgint)
println(status)
completion(status: status)
}
}
}
Placed in the View Controller when attempting to run the function
DataManager.signUp("asdasd", completion: {
(status) in
println(status)
})

Resources