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!
Related
i am new to swift programming and i am working on a login and registry xcode example i found online.
it said, it works with all sort of backends. so i changed it to work with my login.php file. but i come only so far ...
func handleResponse(for request: URLRequest,
completion: #escaping (Result<[User], Error>) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
guard let unwrappedResponse = response as? HTTPURLResponse else {
completion(.failure(NetworkingError.badResponse))
return
}
print(unwrappedResponse.statusCode)
switch unwrappedResponse.statusCode {
case 200 ..< 300:
print("success")
default:
print("failure")
}
if let unwrappedError = error {
completion(.failure(unwrappedError))
return
}
if let unwrappedData = data {
do {
let json = try JSONSerialization.jsonObject(with: unwrappedData, options: [])
print(json)
if let users = try? JSONDecoder().decode([User].self, from: unwrappedData) {
completion(.success(users))
} else {
let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: unwrappedData)
completion(.failure(errorResponse))
}
} catch {
completion(.failure(error))
}
}
}
}
task.resume()
}
i have this request function.
func request(endpoint: String,
loginObject: Login,
completion: #escaping (Result<User, Error>) -> Void) {
guard let url = URL(string: baseUrl + endpoint) else {
completion(.failure(NetworkingError.badUrl))
return
}
var request = URLRequest(url: url)
do {
let loginData = try JSONEncoder().encode(loginObject)
request.httpBody = loginData
print(loginObject)
} catch {
completion(.failure(NetworkingError.badEncoding))
}
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
handleResponse(for: request, completion: completion)
}
and then got this error
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
so i was searching online, mostly in here, what went wrong. i tested the code and found out where the error comes from and i found out, i should change my code to...
if let unwrappedData = data {
do {
let json = try JSONSerialization.jsonObject(with: unwrappedData, options: [])
print(json)
if let users = try? JSONDecoder().decode([User].self, from: unwrappedData) {
completion(.success(users))
} else {
let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: unwrappedData)
completion(.failure(errorResponse))
}
} catch {
completion(.failure(error))
}
}
so i thought that is the proper fix for my problem. but unfortunately i got another error after this change...
Member 'success' in 'Result<User, Error>' produces result of type 'Result<Success, Failure>', but context expects 'Result<User, Error>'
and i cant even build and run the code anymore. can anybody help me?
if necessary i change my login.php from an array to dictionary.
is this the completion closure?
enum MyResult<T, E: Error> {
case success(T)
case failure(E) }
func handleResponse(for request: URLRequest,
completion: #escaping (Result<User, Error>) -> Void) {
... }
enum NetworkingError: Error {
case badUrl
case badResponse
case badEncoding }
ErrorResponse.swift
import Foundation
struct ErrorResponse: Decodable, LocalizedError {
let reason: String
var errorDescription: String? { return reason } }
Login.swift
import Foundation
struct Login: Encodable {
let username: String
let password: String }
User.swift
import Foundation
struct User: Decodable {
let user_id: Int
let username: String
let password: String
let firstname: String
let surname: String
let activated: Int
let reg_time: String
}
print(json) ...
({
activated = 1;
firstname = Thomas;
password = Maggie;
"reg_time" = "0000-00-00 00:00:00";
surname = Ghost;
"user_id" = 2;
username = "testuser";
})
now i am almost back in business. i found out that my user_id in mysql is int(3) but swift 5 doesnt take it as it should. when i take out let user_id: int, i finally get rid off the last error message. but now i can log in with any click of a button and any user and password, whether its right or wrong.
I believe the issue lies in the signature of your completion closure. Looks like you are using Swift.Result but varying the generic Success and Failure types. Can you post the enclosing function where you are passing completion closure?
OK, try changing handleResponse to:
unc handleResponse(for request: URLRequest,
completion: #escaping (Result<[User], Error>) -> Void) {
... }
And ensure that ErrorResponse implements Error
This is my code and I am unable to compile it .
I am trying to login to my server but it doesnt allow me so
Sorry I am new at programming and I've researched on stackoverflow, regarding this error , I can only parse Dictionary or Array. but I've googled by copying my JSON response but it doesnt work.
Any Suggestions would be helpful !!
import UIKit
class Login: UIViewController {
#IBOutlet var Username: UITextField!
#IBOutlet var Password: UITextField!
#IBAction func Login(sender: UIButton) {
let username=Username.text
let password=Password.text
let URL_LOGIN="http://172.22.95.116/SoilCondition/app/getLogin.php?USERNAME=" + username! + "&PASSWORD=" + password!;
let requestURL = NSURL(string: URL_LOGIN)
let request = NSMutableURLRequest(URL: requestURL!)
request.HTTPMethod = "POST"
let postParameters = "username="+username!+"&password="+password!;
request.HTTPBody = postParameters.dataUsingEncoding(NSUTF8StringEncoding)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
data, response, error in guard let data = data where error == nil
else {
print("error: \(error)")
return
}
do{
let myJSON = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
if let parseJSON = myJSON{
var msg: String!
msg = parseJSON["message"] as! String?
print(msg)
}
/* if let parseJSON = myJSON {
var msg : String!
msg = parseJSON["message"] as! String?
print(msg)
}*/
/*if data != nil {
json = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
println("json: \(json)")
if let dictionary = parseJSON(jsonString) {
println("dictionary: \(dictionary)")
}*/
} catch let parseError{
print(parseError)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I found this question regarding error code 3840.
As it says there, the problem could be that your server doesn't return valid JSON to you.
Now, you say:
I can only parse Dictionary or Array
I don't know if that means that you are able to actually parse the response you receive from the server into valid JSON here:
let myJSON = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
But if it doesn't then a good place to start could be to verify whether your server actually returns valid JSON to you.
To do so, you could try calling your server directly from cURL or postman and see what you get in return.
Hope that helps you.
So basically I have a login script on my server that returns a result depending on if the user credentials are correct or wrong, and I have an iOS App that sends data to that login script to return the correct or wrong result.
Here is the relevant part of my login page that shows the return code ($userDetails being the TRUE or FALSE check of correct or wrong credentials) :
$userDetails = $dao->getUserDetailsWithHashedPassword($email,$password);
if($userDetails===TRUE) {
$returnValue["status"] = "Success";
$returnValue["message"] = "User logged in !";
echo json_encode($returnValue);
} else {
$returnValue["status"] = "error";
$returnValue["message"] = "User not found";
echo json_encode($returnValue);
}
If anyone needs to see what that getUserDetailsWithHashedPassword() does, click here
Using Postman to test the HTTP POST, everything works fine, I get the correct result when posting email#email.com & testpassword in the body and using the correct Content-Type (application/x-www-form-urlencoded) :
{"status":"error","message":"User not found"}
Now my iOS is supposed to interpret this with this code :
#IBAction func loginButtonPressed(_ sender: AnyObject) {
let userEmail = emailLoginField.text
let userPassword = passwordLoginField.text
// Check for empty fields
if((userEmail?.isEmpty)! || (userPassword?.isEmpty)!) {
// Display alert message
displayMyAlertMessage(userMessage: "All fields are required");
return ;
}
// Send user data to server side
let myUrl = URL(string: "https://support.vincentsolutions.ca/userLogin.php");
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let postString = "email=\(userEmail!)&password=\(userPassword!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
URLSession.shared.dataTask(with: request, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) -> Void in
if error != nil {
print ("error=\(error)")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
var resultValue = parseJSON["status"] as? String
print("result: \(resultValue)")
var isUserLoggedIn:Bool = false;
if(resultValue=="Success") {
// Login is successful
UserDefaults.standard.set(true, forKey: "isUserLoggedIn");
UserDefaults.standard.synchronize();
self.performSegue(withIdentifier: "loginSuccesful", sender: self)
}
var messageToDisplay:String = parseJSON["message"] as! String!;
if(!isUserLoggedIn) {
messageToDisplay = parseJSON["message"] as! String!;
}
DispatchQueue.main.async(execute: {
// Display alert message with confirmation.
var myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default){ action in
self.dismiss(animated: true, completion: nil);
}
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil);
});
}
} catch let error as NSError {
print("An error occured: \(error)")
}
}).resume()
Now I'm getting this error when I run the code from the iOS App :
An error occured: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text
did not start with array or object and option to allow fragments not
set."
Does anyone see what could be wrong here ? I've tried looking for that error here on SO and on the internet but couldn't find anything related to my situation.
That is because the response you are receiving from the URL probably not in correct JSON format. I will suggest you to do 2 things -
Try to NSLog the data and response
Try this
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary
I am trying to get login/register on my iOS app to work. I am using mySQL and PHP besides Swift 2.0. For some reason when I try to send my HTTP POST to the PHP-scripts I keep geting the error: "The data couldn’t be read because it isn’t in the correct format."
I am using MAMP for the mySQL server.
let request = NSMutableURLRequest(URL: NSURL.fileURLWithPath("/Users/robin/Programming/xcode/Projects/Quix/php_scripts/userRegister.php"))
request.HTTPMethod = "POST"
let postString = "email=\(userEmail)&password=\(userPassword)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
print(postString)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
if (error != nil) {
print("error=\(error)")
}
do {
if let parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
let 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(), {
let myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { action in
self.dismissViewControllerAnimated(true, completion: nil)
}
myAlert.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion: nil)
});
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
In my database I have the table 'users' and the parameters id (auto_increment), email and password. I am using port: 3306 for mySQL aswell. The standard IP for MAMP is 127.0.0.1, should I use the IP: 127.0.0.1 or localhost:3306 aswell?
You are using a file URL with a NSURLSession request. You should use a https:// or http:// request. NSURLSession is for making network requests, not local file system requests. So, if you're running this on the iOS simulator, you can use http://localhost/.... But when you run it on an device, you'll have to supply it a host name that will resolve to your machine running MAMP.
By the way, if you use http, you may need to add a NSExceptionDomains entry as outlined in https://stackoverflow.com/a/31254874/1271826.
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()
}
}
}