I have simple login method which returns bool, depends on success of user login. I have problem with order of the responses and execution of the code. I've read about completion handlers, which I think are a solution to my problem but I'm not sure. Here is my method:
//perform user login in, setting nsuserdefaults and returning the bool result
func login(username: String, password:String) -> (Bool) {
var success:Bool = false
//sending inputs to server and receiving info from server
let postRequest = postDataToURL()
postRequest.link = "http://pnc.hr/rfid/login.php"
postRequest.postVariables = "username=" + username + "&password=" + pass
word
postRequest.forData("POST") { jsonString in
// getting the result from the asinhronys task
let result = convertStringToDictionary(jsonString as String)
if let loggedIn = result?["loggedIn"] as? Bool where loggedIn == true {
let userType = result?["userType"] as? String
let token = result?["token"] as? String
//if user is logged - setting parameters in Key Chains and redirecting them to the menu view
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(loggedIn, forKey: "loggedIn")
defaults.setObject(username, forKey: "username")
defaults.setObject(userType, forKey: "userType")
defaults.setObject(token, forKey: "token")
success = true
}
else {
success = false
}
print ("class - " + String(jsonString))
print ("classIN - " + String(success))
}
print ("classOUT - " + String(success))
return success
}
I would like to make return of success variable inside if statement which checks variable loggedIn is equal to true. But in that case I get error.
Then I have made this method. The problem is that method returns the variable success quicker than the POST request has been done. So it will be false in every case. I have printed variables to see the order of the code execution and method first prints the "classOUT", returns the variable, and then sets up variable value and print "classIN".
How can I wait until the code which logs user gets executed so I can get the right value of the variable success?
Perform user login in, setting nsuserdefaults and returning the bool result
completionBlock: is that block which will get executed when you call it like any block but you get to choose when and what all to pass through that block.
func login(username: String, password:String,completionBlock : ((success : Bool)->Void)){
//sending inputs to server and receiving info from server
let postRequest = postDataToURL()
postRequest.link = "http://pnc.hr/rfid/login.php"
postRequest.postVariables = "username=" + username + "&password=" + password
postRequest.forData("POST") { jsonString in
// getting the result from the asinhronys task
let result = convertStringToDictionary(jsonString as String)
if let loggedIn = result?["loggedIn"] as? Bool where loggedIn == true {
let userType = result?["userType"] as? String
let token = result?["token"] as? String
//if user is logged - setting parameters in Key Chains and redirecting them to the menu view
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(loggedIn, forKey: "loggedIn")
defaults.setObject(username, forKey: "username")
defaults.setObject(userType, forKey: "userType")
defaults.setObject(token, forKey: "token")
completionBlock(success:true)
}
else {
completionBlock(success:false)
}
}
}
when you call it would look something like this:-
login(username: String, password:String,completionBlock : { (success) in
print(success)
})
you could do something like this
func login(username: String, password: String, completion: (Bool) -> ()) {
... YOUR USUAL NETWORKING CODE ...
completion(success)
}
and then call it
login(username: anonymous, password: ******) { authStatus in
if authStatus == true {
print("user in")
} else {
print("try one more time")
}
}
Related
I followed this guide on how to setup a SLComposeViewController, and it includes on how to set up configuration items.
I'm pulling from a MongoDB database to get the most recently modified item. Here is my configurationItems():
override func configurationItems() -> [Any]! {
let configurationItems = [editConfigurationItem]
return configurationItems
}
and here is my editConfigurationItem variable:
lazy var editConfigurationItem: SLComposeSheetConfigurationItem = {
func getFirstCategory(completion: #escaping(String) -> ()) {
DispatchQueue.main.async {
let email: String = customKeychainWrapperInstance.string(forKey: "email") ?? ""
let password: String = customKeychainWrapperInstance.string(forKey: "password") ?? ""
app.login(credentials: Credentials.emailPassword(email: email, password: password)) { (maybeUser, error) in
DispatchQueue.main.sync {
guard error == nil else {
print("Login failed: \(error!)");
return
}
guard let _ = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
}
let user = app.currentUser!
let configuration = user.configuration(partitionValue: user.id)
let predata = try! Realm(configuration: configuration)
let unsortedData = predata.objects(Tiktoks.self)
let data = unsortedData.sorted(byKeyPath: "date", ascending: false)
let firstCategory = data[0].category
let firstCategoryId = data[0]._id
print(firstCategory)
print(firstCategoryId)
self.selectedId = firstCategoryId
completion(firstCategory)
}
}
print("done")
}
let item = SLComposeSheetConfigurationItem()!
item.title = "collection"
item.valuePending = true
getFirstCategory() { (firstCategory) in
item.valuePending = false
print("completed")
item.value = firstCategory // Using self as the closure is running in background
}
item.tapHandler = self.editConfigurationItemTapped
return item
}()
(Sorry for the messiness of the code, I'm new to Swift)
So far it works, but the item.value variable doesn't get updated in the UI. It "infinitely loads" until you click on the configuration item. When the configuration item is tapped to go to another view though, the actual variable from the database shows for a split second before showing the next view. When you go back from that other view, the actual variable is there.
It looks like to me that the configuration item isn't updating. I see that there is a reloadConfigurationItems(), but by putting that in my editConfigurationItem will cause a loop I would think (and it also errors out too). The documentation even says:
In particular, you don’t need to call this method after you change a configuration item property, because the SLComposeServiceViewController base class automatically detects and responds to such changes.
but it looks like it's not detecting the changes.
Is there a way to refresh item.value and item.valuePending?
Hi I am new in iOS development and I am having hard time to understand the following issue. Basically I am trying to get user's name by passing current user's id to Cloud Firestore. However I am having hard time to understand a bug in the code. I can successfully pass the name of user to name variable, while the function returns default value of name which is "" (empty string). It seems that the block of code inside
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
happens later than
print("name") // this line
return name
Full code:
private func returnCurrentUserName() -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
(Note: the query from Cloud Firestore is successful and I can get users name on the console but "name" is printed after "after guard".)
In addition to the other answer:
If you would like to execute code after your operation is done, you could use a completion block (that's just a closure which gets called upon completion):
private func returnCurrentUserName(completion: #escaping () -> ()) -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
completion()//Here you call the closure
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
How you would call returnCurrentUserName:
returnCurrentUserName {
print("runs after the operation is done")
}
Simplified example:
func returnCurrentUserName(completion: #escaping () -> ()) -> String {
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
completion() //runs after 4 seconds
}
return "xyz"
}
let test = returnCurrentUserName {
print("runs after the operation is done")
}
print(test)
The reason is your getDocument is an asynchronous operation. It takes a callback, and that callback will be invoked when the operation is done. Because of the asynchronous operation, the program will continue process the next line without waiting for the async operation to be completed. That's why you see your print("name") getting executed before the print("after guard")
I have an application which uses a rest api for authentication. The problem I am facing now is that I save user's token in my UserDefaults and username too because those are the two main parameters needed to get user details. so if the application is closed by the user he should still be able to view the view his profile when he opens the application back but instead the profile returns empty details. this is the UserDefaults codes that I have
let defaults = UserDefaults.standard
var isLoggedIn : Bool {
get {
return defaults.bool(forKey: LOGGED_IN_KEY)
}
set {
defaults.set(newValue, forKey: LOGGED_IN_KEY)
}
}
//Auth Token
var authToken: String {
get {
return defaults.value(forKey: TOKEN_KEY) as? String ?? ""
}
set {
defaults.set(newValue, forKey: TOKEN_KEY)
}
}
var userUsername: String {
get {
return defaults.value(forKey: USERNAME_KEY) as? String ?? ""
}
set {
defaults.set(newValue, forKey: USERNAME_KEY)
}
}
I have no idea why it isnt retrieving the user data.
My second question is when I logout the user, all the users details are cleared as expected but the moment I try loging in with a different user, the new user's authToken and details gets printed in the console but the user profile returns the profile of the previous person. which is not supposed to be. my code is shown below
func logoutUser() -> Void {
pk = 0
username = ""
email = ""
firstName = ""
lastName = ""
AuthService.instance.isLoggedIn = false
AuthService.instance.authToken = ""
AuthService.instance.userUsername = ""
}
#IBAction func logoutPressed(_ sender: Any) {
UserDataService.instance.logoutUser()
dismiss(animated: true, completion: nil)
}
I would also like to add that when i run the api using postman i get a response that "detail": "Signature has expired." so i had to input the new token in the header so it displays the user details again
enum SettingKeys: String {
case authToken
//...
}
struct Settings {
static var authToken: String? {
get { return UserDefaults.standard.string(forKey: SettingKeys.authToken.rawValue) }
set(value) { UserDefaults.standard.set(value, forKey: SettingKeys.authToken.rawValue) }
}
static func deleteAll(exclude: [SettingKeys] = []) {
let saveKeys = exclude.map({ $0.rawValue })
for key in UserDefaults.standard.dictionaryRepresentation().keys {
if !saveKeys.contains(key) {
UserDefaults.standard.removeObject(forKey: key)
}
}
}
}
I recommend storing keys as Enum, because then u can use it like that:
//Read
if let token = Settings.authToken {
//do something
}
//Write
Settings.authToken = "123456"
//Delete settings
Settings.deleteAll()
//or choose what to leave
Settings.deleteAll(exclude: [.authToken])
And it's worth to mention that defaults.synchronize() is deprecated.
I'm trying to login a user by using the username or email. Right now my code works to log the user in by only Username. I'm using a "PFUser.logInWithUsername", but I'd also like to login with the users email. I'm trying to change my code to allow the user to have the choice to either email or a username. Here is my code.
#IBAction func LogInButton(_ sender: AnyObject) {
// login functions
PFUser.logInWithUsername(inBackground: UsernameOrEmail.text!, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
// remember user or save in App Memeory did the user login or not
UserDefaults.standard.set(user!.username, forKey: "username")
UserDefaults.standard.synchronize()
// call login function from AppDelegate.swift class
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
// Delay the dismissal by 5 seconds
let delay = 1.0 * Double(NSEC_PER_SEC)
var time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time, execute: {
appDelegate.login()
})
} else {
}
You can use a regex pattern to detect if the user enters an email. If the detection returns false, then you "know" that the user entered their username.
This is what I use in my applications(works ok):
//A function that returns true or false based on the input. True if email, false if something else.
func isValidEmail(email:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: email)
}
//check if user enters email or not:
if isValidEmail(email: user!.username){
//email adress detected
}else{
//username detected
}
EDIT:
As I understand your problem the function above will solve your problem. I have provided a some code for you to test out.
I do not know what PFUser is capable of, but I assume there is a function for login with username and another for email.
//A function that returns true or false based on the input. True if email, false if something else.
func isValidEmail(email:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: email)
}
#IBAction func LogInButton(_ sender: AnyObject) {
//check if user enters username or email
if let usercredentials = UsernameOrEmail.text { //get the username from textfield
if isValidEmail(email: usercredentials){
//user did enter his email as login credential
PFUser.logInWithEmail(inBackground: usercredentials, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
//do your login stuff here
} else {
}
}else{
//user did enter his username as login credential
PFUser.logInWithUsername(inBackground: usercredentials, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
//do your login stuff here
} else {
}
}else{
//textfield not accessible
}
}
What I want to do: I have a login screen, the user fill the username and password, and then it press the login button. An async call to the server is done to check if user is registered and password is okay, and if yes (async function set a bool to yes) then do a segue to the next view controller. Simple as that, I've tried many ways but with always the same problem, the main thread runs the shouldPerformSegueWithIdentifier method, do the async call and check the global bool var (false by default) before the background thread has updated it, so the segue is not performed because the global variable is set to true AFTER. Only if I use sleep(1) the UI is refreshed but I don't want to use this. Is there a way to do this without sleep?? Every method I run has a completion handler.
I don't know how to sync the main with the background thread. I've read it's posible to update UI from async call so this should be posible. I've been looking questions for a while and tried lot of snippets, and still haven't found a solution for my problem.
This is the code I have so far:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
let apiCall = webApi()
dispatch_async(dispatch_get_main_queue()) {
apiCall.callCheckIsUserLogged(nil, password : self.passwordField.text, email: self.mailField.text){ (ok) in
}
}
//sleep(1) if I uncomment this, my method works because it will return true
return userIsLogged
}
apiCall.callCheckIsUserLogged() :
typealias successClosure = (success : Bool) -> Void
//Make a call to check if the user exist, server returns json with success or failure
func callCheckIsUserLogged(username: String?, password : String?, email: String?,completed : successClosure){
userApiCallUrl = "http://apiurl.com/users/login"
let call = asyncCallClass()
call.doAsyncCallWithParams(userApiCallUrl, calltype: "POST", username: username, pass: password, mail: email){ (success) in
completed(success: true)
}
}
call.doAsyncCallWithParams() code:
internal typealias completion = (success : Bool) -> Void
private var flagCompletion : Bool = false
//Handle async class with this method
//var callType is aditioned everytime an arg is not passed nil.
//callType == 3 it is a call to check if user is logged
//callType == 2 is a call to register a new user
func doAsyncCallWithParams(url : String, calltype : String, username : String?, pass : String?, mail : String?, completed : completion){
var callType : Int = 0
//Set Async Url
setUrl(url)
//Set Post Params
if let user : String = username{
self.username = "username=\(user)"
callType += 1
}
if let password : String = pass{
self.password = "password=\(password)"
callType += 1
}
if let mail : String = mail{
self.email = "email=\(mail)"
callType += 1
}
//register a new user
if(callType == 3){
paramString = "\(self.username)&\(self.password)&\(self.email)"
}
//check if user is logged, send email and password
if(callType == 2){
paramString = "\(self.email)&\(self.password)"
}
//Do call
callWithCompletionHandler { (success) in
self.flagCompletion = true
completed(success: self.flagCompletion)
}
}
callWithCompletionHandler() code:
private typealias completionAsyncCall = (success : Bool) -> Void
private func callWithCompletionHandler(completed : completionAsyncCall){
asyncJson.removeAllObjects()
//Set async call params
let request = NSMutableURLRequest(URL: NSURL(string: self.url!)!)
request.HTTPMethod = "POST"
let trimmedPostParam : String = self.paramString!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
request.HTTPBody = trimmedPostParam.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else {
// check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 {
// check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
let result : AnyObject = responseString!.parseJSONString!
if let nsMutableResult = result as? NSMutableArray{
print("NSMutableArray")
}
if let nsDictResult = result as? NSMutableDictionary{
self.parseMutableDictionary(nsDictResult)
}
self.flag = true // true if download succeed,false otherwise
completed(success: flagAsyncCall!)
}
task.resume()
}
On login button press call :
apiCall.callCheckIsUserLogged(nil, password : self.passwordField.text, email: self.mailField.text){ (ok) in
if ok {
self.performSegueWithIdentifier("Identifier", sender: self)
} else {
print("User not logged in")
}
}
Because your callCheckIsUserLogged method already returns if user logged in or not.
internal typealias completion = (success : Bool) -> Void