I am having some difficulty with an asynchronous data fetch from a server (MySQL database which I access through some PHP web services). My code is below:
func RetreiveStaff() {
SetProgramMode()
self.astrUsers = [String]()
self.pkvUser.reloadAllComponents()
if self.booCurrentDataVersion == true {
var url:NSURL = NSURL(string: "http://www.website.com.au/retrievestaff.php")!
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data:NSData!, response:NSURLResponse!, error:NSError!) -> Void in
if error == nil {
let dataArray:[AnyObject] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! [AnyObject]
for data in dataArray {
let dictionary:[String:String] = data as! [String:String]
if dictionary["StaffID"] != nil {
self.astrUsers.append(dictionary["LastName"]! + ", " + dictionary["FirstName"]!)
self.astrUserIDs.append(dictionary["StaffID"]!)
self.pkvUser.reloadAllComponents()
}
self.pkvUser.reloadAllComponents()
}
} else {
let actionSheet:UIAlertController = UIAlertController(title: "Connection Error", message: "\(strAppName) was unable to load data. Check you are connected to the internet and restart the app.", preferredStyle: UIAlertControllerStyle.Alert)
let firstAlertAction:UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: {
(alertAction:UIAlertAction!) in
})
actionSheet.addAction(firstAlertAction)
self.presentViewController(actionSheet, animated: true, completion: nil)
}
})
task.resume()
}
}
When the code executes, the data is fetched as it should, but the view doesn't display it. Basically, as you can see, I grab the data, put it in an array and then reload my pickerView (which uses the array as its datasource). The pickerView remains visibly empty for about 20 seconds after the code is executed, and then suddenly appears. However, if the user taps on, drags, changes the value of, etc. the pickerView during the 20 seconds, the data appears.
From my understanding of the problem, this means that the data is being fetched and put into the pickerView but the view isn't reloading correctly, despite my numerous and probably excessive number of self.pkvUser.reloadAllComponents().
What am I doing wrong?
(I have also checked out these questions but they haven't solved the issue:
Swift: Asynchronous callback
Asynchronous Fetching Swift Xcode
iOS Swift: Displaying asynchronous data on a TableView )
displaying any view in IOS should ALWAYS be in the main thread.
when you are doing async requests, push the results to the user's view from main thread like this:
var someView : UIView = someAsyncCallBack..
dispatch_async(dispatch_get_main_queue()) {
currentView.addSubview(someView);
}
try this code, after the async request is done, there's no problem to handle the response in the main queue, this also solves the display lag
if error == nil {
dispatch_async(dispatch_get_main_queue()) {
let dataArray:[AnyObject] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! [AnyObject]
for data in dataArray {
let dictionary:[String:String] = data as! [String:String]
if dictionary["StaffID"] != nil {
self.astrUsers.append(dictionary["LastName"]! + ", " + dictionary["FirstName"]!)
self.astrUserIDs.append(dictionary["StaffID"]!)
self.pkvUser.reloadAllComponents()
}
self.pkvUser.reloadAllComponents()
}
}
}
Related
I am trying to retrieve data from my own API, if i try to connect to my API using chrome browser, it gives JSON data back like this
{"id":"52","username":"aasad23","fullname":"aasad
laksana","email":"aasad#gmail.com","avatar":"/Applications/XAMPP/xamppfiles/htdocs/Twitter/Avatar/52/avatar.jpg"}
but, when I tried to access the API through my iOS app, it gives an error while doing JSON serialization, it give an error:
the data couldn’t be read because it isn’t in the correct format
what does it mean the correct format?
I have checked that the code error in this line
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
Thats why the catch error is activated and give that error message.
here is the full code of this task
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new parseJSON to store json
guard let parsedJSON = json else {
print("Error while parsing")
return
}
print(parsedJSON)
// get id from $returnArray["id"] in PHP - parseJSON["id"]
let id = parsedJSON["id"]
// successfully uploaded
if id != nil {
// save user information yang berasal dari server
UserDefaults.standard.set(parsedJSON, forKey: "parsedJSON")
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = parsedJSON["message"] as! String
self.showAlert(alertTitle: "opppps", alertMessage: message, actionTitle: "OK")
})
}
// JSON serialization error
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error.localizedDescription
self.showAlert(alertTitle: "Sorry", alertMessage: message, actionTitle: "OK")
})
}
// error when connecting to server
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
self.showAlert(alertTitle: "oppps", alertMessage: message, actionTitle: "OK")
})
}
})
}.resume()
}
try
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
rather than
try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
I'm trying to retrieve simple text data out of my database. When the app runs, you type in whatever you want and it saves it to the database. That part is working. When i log in to phpMyAdmin, that data is there. However, I'm trying to show the data as well, right above the textfield where the user inputs their text. When the app runs, there is nothing there. Here is my code for my retrieve data function. Thank you
func retrieveMessages(latestMessage:String) {
// Specify the URL of our retrieval web service
let url:NSURL = NSURL(string: "http://mypage.com/index.php")!
// Create a NSURLSession task with completion handler
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
do {
// Convert the json data into an array
let dataArray:[AnyObject] = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [AnyObject]
// Clear the messages array
self.messagesArray.removeAll(keepCapacity: false)
// Loop through each dictionary in the array
for data in dataArray {
let dictionary:[String:String] = data as! [String:String]
// Append it to our messages array
if dictionary["Text"] != nil {
self.messagesArray.append(dictionary["Text"]!)
}
}
dispatch_async(dispatch_get_main_queue()) {
// Refresh the table
self.tableView.reloadData()
}
}
catch{}
})
// Run the task
task.resume()
}
I know this question has been asked dozens of times before but unfortunately I'm not knowledgable enough with Swift to adapt those answers to my issue.
Basically I have a function that sends a POST request to a php script on a server but the response seems to take longer to receive than it does for my script to utilise the result. It sends a number variable and receives one as a response so I am also converting it from an 'any_object' to an 'NSNumber'.
From what I understand I need to implement a completion handler but I can't seem to figure out how to get it to work with my code below.
var dataResult: Int = 0
var dataReceived: NSNumber?
func remoteRand() {
let dataToSend = 1
let myUrl = NSURL(string: "http://localhost/scripts/phpScript.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "dataToSend =\(dataToSend)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_main_queue()) {
if (error != nil) {
self.displayAlertMessage((error?.localizedDescription)!)
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json {
let dataReceived = parseJSON["dataValue"]
self.dataResult = (dataReceived as! NSNumber).integerValue
print("\(self.dataResult) is server response")
}
} catch { print(error)}
}
}
task.resume()
processResult()
}
func processResult() {
print(dataResult)
}
The result I get is:
0
1 is server response
How do I either make it wait for the response before proceeding to the next function or trigger the next function once completion is confirmed?
Thanks in advance!
The task completes on a different thread than which it is called, this is by design so network requests don't stop execution on the main thread. So your block of code is sent off async and processResult is called before the network request is finished. You can put the call in the do block or add a closure to the method that can be called.
func remoteRand(completion: (response: Int) ->()) {
// your code
// process JSON
// get value
completion(dataReceived)
// is passed back, just like the dataTaskWithRequest method to your caller where you can set the property on self, etc.
}
using it somewhere:
remoteRand { [weak self] (response) in
self?.dataResult = response
}
I'm fairly new to programming with Swift in Xcode so I have been recently following the fantastic php sql sign up tutorial by Sergey Kargopolov located here http://swiftdeveloperblog.com/store-user-information-in-mysql-database/.
Unfortunately the tutorial was slightly outdated and needed to be updated for Swift 2.0. I managed to substitute the problematic code by implementing a do/catch statement which seems to work great except when i receive a response from the server side php script I can't seem to navigate to different view controllers based on the result. For example if the response is "Registration Successful" the user is directed to the protected page however if the email address already exists in the database, I want it to stay on the same page.
Currently I have the code working to the point where the alert appears (either user already exists or successful) but when the user clicks "Ok", they are directed to the protected page regardless of the result. I figured it would be a simple case of telling it to go to the next view based on the result in this "if" statement:
if (resultValue == "Success") {
isUserRegistered = true
let next = self.storyboard?.instantiateViewControllerWithIdentifier("ProtectedViewController") as! ProtectedViewController
self.presentViewController(next, animated: true, completion: nil)
}
but it doesn't seem to work. Hopefully it makes sense what i'm trying to do and any help is greatly appreciated.
here is my code:
let myUrl = NSURL(string: "http://localhost/PhpProject1/scripts/registerUser.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "userEmail=\(userEmail!)&userFirstName=\(userFirstName)&userLastName=\(userLastName)&userPassword=\(userPassword!)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json {
var resultValue = parseJSON["status"] as! String!
print("result: \(resultValue)")
var isUserRegistered: Bool = false
if (resultValue == "Success") {
isUserRegistered = true
}
var messageToDisplay: String = parseJSON["message"] as! String!
if (!isUserRegistered)
{
messageToDisplay = parseJSON["message"] as! String!
}
dispatch_async(dispatch_get_main_queue(), {
var myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default) { action in
// self.dismissViewControllerAnimated(true, completion: nil)
let next = self.storyboard?.instantiateViewControllerWithIdentifier("ProtectedViewController") as! ProtectedViewController
self.presentViewController(next, animated: true, completion: nil)
}
myAlert.addAction(okAction)
}
)}
} catch { print(error)}
}
task.resume()
}
func displayAlertMessage(userMessage:String) {
let myAlert = UIAlertController(title: "Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
}
}
I should also add that when the user registers successfully, I get "Result: 200" in the output window but when the user email already exists I get "Result: 400". I just don't know how to take advantage of this.
Thanks in advance!
From what I can see from your JSON response, parseJSON["status"] is the HTTP statusCode, which is a numerical status code. 200 being success, 400 being bad request, 404 being Not found, 500 being an error.
You could either read the message from the dictionary instead or depending on how your PHP code is working you could cast the status as an Integer and only segue when status is 200. For any other status code you likely want to show the message to the user.
This depends on whether you are intentionally returning status codes based on the result, for example a status code of 401 is un-authorised.
Using the status code properly would be much more reliable than comparing strings.
EDIT:
import Foundation
let jsonData = "{ \"message\": \"Successfully registered new user\", \"status\": 200, \"userEmail\": \"abc\", \"userFirstName\": \"a\", \"userId\": 22, \"userLastName\": \"a\" }".dataUsingEncoding(NSUTF8StringEncoding)
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json {
let statusCode = parseJSON["status"] as! Int
switch statusCode {
case 200:
print("successfully logged in")
case 400, 401:
print("access denied")
default:
print("error occurred")
}
}
}
Above, I have added an example of how you might approach this in Swift, you just need to change the behaviour depending on which statusCodes you want to deal with and how
Well, I stayed up a little longer and thanks to you Scriptable I was able to figure it out.
Basically all I did was create a new variable:
var statuscheck: Int!
And then after the "if let parseJSON = json {" statement I cast the "status" as an Int like this:
self.statuscheck = (parseJSON["status"]! as! NSString).integerValue
Then after the line beneath the "dispatch_async(dispatch_get_main_queue()":
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default) { action in
I did the check:
if (self.statuscheck == 200) {
let next = self.storyboard?.instantiateViewControllerWithIdentifier("ProtectedViewController") as! ProtectedViewController
self.presentViewController(next, animated: true, completion: nil)
}
This seems to work great now.
Thanks again for all your help Scriptable. You definitely pointed me in the right direction!
i need help with my app login code.
I try to make a Http Request to my local server, a simple login request with mail and password but i've a issue and i don't know how to fix it.
#IBAction func clickConnexionBtn(sender: AnyObject)
{
if(!mailTf.text!.isEmpty && !passwordTf.text!.isEmpty)
{
let mail = mailTf.text!
let password = passwordTf.text!
let param = String(format: "mail=%#&password=%#", arguments: [mail, password]);
let url = NSURL(string: "http://localhost:8888/MoneyManager/login.php")!
RequestObject.sharedInstance.prepareRequest(param, _url: url, _method: "POST")
let task = NSURLSession.sharedSession().dataTaskWithRequest(RequestObject.sharedInstance.request)
{ data, response, error in
if error != nil
{
let alertView = UIAlertController(title: "Error server", message: "Could not connect to server", preferredStyle: UIAlertControllerStyle.Alert)
self.showViewController(alertView, sender: self)
}
else
{
let serverResponse = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Server response: \(serverResponse)")
do
{
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? NSDictionary {
print(json)
}
} catch let error2 as NSError {
print(error2.description)
}
}
}
task.resume()
}
}
So this is the action when i click on the 'connexionButton'.
Unfortunately when i try to Serialize the JSON, i have this error:
Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}
My PHP code is like:
if($result)
{
$return = array("Success" => 1, "Message" => "Insert user done", "user_id" => $_SESSION['id'], "api_key" => $_SESSION['api_key']);
echo json_encode($return);
}
Here is the code of my 'print("Server ....")'
Server response: Optional(Connexion successfully {"Success":1,"Message":"Insert user done","user_id":"9","api_key":"aa3b0c16fc63efe207be1a7471ac234a"})
Its all the data that i want to get and turn into JSON object
I look many subject but no one of them could help me cause most of the people use Alamofire to perform request.
So please if you have an idea, i listening
Thanks all.