Reputation: 73
So I recently started to migrate my code into MVVM pattern and I am having some issues. Currently I am working on my loginPage which authenticates the user. The problem is my i call my function, but my code doesn't wait for a response from it and moves to the next line. I think thats because of Alamofire's async stuff. Here is my code:
// My login view controller
class loginPageViewController: UIViewController, UITextFieldDelegate {
private var viewModel = userViewModel()
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBAction func signInButton(_ sender: roundButton) {
if(usernameTextField.text != "" && passwordTextField.text != "") {
self.viewModel.validate(nameField: self.usernameTextField.text!, passField: self.passwordTextField.text!)
if(self.viewModel.isDone){
print("success")
self.presentPage(identifier: "homePageNavCtrl")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
usernameTextField.delegate = self
passwordTextField.delegate = self
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
usernameTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
return true;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
private extension loginPageViewController{
func displayErrorMessage(errorMessage: String){
let alert = UIAlertController(title: "Error", message: errorMessage, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func presentPage(identifier: String){
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: identifier)
self.present(nextViewController, animated:true, completion:nil)
}
}
What I want to do is when the sign In button is tapped, validate the user with a JSON request and move to the next page if credentials is correct.
Here is my userViewModel
class userViewModel{
private var mng = restInterface()
private var User = user()
var isDone = false
var id: Int {
return User.ID
}
var username: String {
return User.LOGINNAME
}
var password: String {
return User.PASSHASH
}
}
extension userViewModel{
func updateId(id: Int){
User.ID = id
}
func updateUsername(username: String){
User.LOGINNAME = username
}
func updatePassword(password: String){
User.PASSHASH = password
}
func validate(nameField: String, passField: String){
let accountBaseURL = "myUrlLiesDownHere"
var nm = nameField
var ps = passField
nm.insert("'", at: nm.startIndex)
nm.insert("'", at: nm.endIndex)
ps.insert("'", at: ps.startIndex)
ps.insert("'", at: ps.endIndex)
let params : [String: String] = ["sUser" : nm, "sPass" : ps, "sToken": "''"]
self.mng.performRequest(URLString: accountBaseURL, parameters: params, method: .get, successCallback: { (dict) in
self.User.ID = dict.value(forKey: "ID") as! Int
self.User.LOGINNAME = dict.value(forKey: "LOGINNAME") as! String
self.User.PASSHASH = dict.value(forKey: "PASSHASH") as! String
self.loginDone()
}) { (error) in
print("Error")
}
}
func loginDone(){
self.isDone = true
}
}
And here is my rest interface
class restInterface {
func performRequest(URLString: String, parameters : Parameters, method:HTTPMethod, successCallback: @escaping (NSDictionary) -> Void, errorCallBack: @escaping (String) -> Void) -> Void{
Alamofire.request(URLString, method: method, parameters: parameters).responseJSON { response in
print(response)
if let JSON = response.result.value as? [String: Any] {
print(JSON)
successCallback(JSON as NSDictionary)
} else {
errorCallBack("JSON Doesn't Exist")
}
}
}
}
The isDone boolean on userViewModel must be true when I control it in my view controller. But It doesn't. When I hit the button once, result becomes true but nothing happens. When I hit it again, it redirects me to the next page which I want. So working with async really confused me, and I am digging for help here :) Thanks...
Upvotes: 2
Views: 1386
Reputation: 15258
In you UserViewModel
, create a completionHandler
callback and implement it inside the viewController
to present the next viewController
.
class UserViewModel {
public var loginCompletion: (()-> Void)?
private var mng = restInterface()
private var User = user()
// rest of your code
}
Calling the completionHandler
once the request if finished,
extension userViewModel{
func validate(nameField: String, passField: String){
let accountBaseURL = "myUrlLiesDownHere"
var nm = nameField
var ps = passField
nm.insert("'", at: nm.startIndex)
nm.insert("'", at: nm.endIndex)
ps.insert("'", at: ps.startIndex)
ps.insert("'", at: ps.endIndex)
let params : [String: String] = ["sUser" : nm, "sPass" : ps, "sToken": "''"]
self.mng.performRequest(URLString: accountBaseURL, parameters: params, method: .get, successCallback: { (dict) in
self.User.ID = dict.value(forKey: "ID") as! Int
self.User.LOGINNAME = dict.value(forKey: "LOGINNAME") as! String
self.User.PASSHASH = dict.value(forKey: "PASSHASH") as! String
self.loginDone()
self.loginCompletion?()
}) { (error) in
print("Error")
}
}
func loginDone(){
self.isDone = true
}
}
completionHandler
usage inside the viewController
,
class loginPageViewController: UIViewController, UITextFieldDelegate {
private var viewModel = userViewModel()
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
usernameTextField.delegate = self
passwordTextField.delegate = self
self.viewModel.loginCompletion = { [weak self] in
print("success")
self?.presentPage(identifier: "homePageNavCtrl")
}
}
@IBAction func signInButton(_ sender: roundButton) {
if(usernameTextField.text != "" && passwordTextField.text != "") {
self.viewModel.validate(nameField: self.usernameTextField.text!, passField: self.passwordTextField.text!)
}
}
// rest of your code
}
Notes: In Swift, class/struct/enum
name should start with Capital letter . The above solution is a very basic setup for observing data between view-viewModel. You should look for a much better data binding solution e.g, Rx-Swift, Bond, Two-Way Binding etc.
Upvotes: 3
Reputation: 1058
---- Change in validate method -
func validate(nameField: String, passField: String, completionHandler: @escaping (_: NSDictionary?, _: NSError?) -> Void){
let accountBaseURL = "myUrlLiesDownHere"
var nm = nameField
var ps = passField
nm.insert("'", at: nm.startIndex)
nm.insert("'", at: nm.endIndex)
ps.insert("'", at: ps.startIndex)
ps.insert("'", at: ps.endIndex)
let params : [String: String] = ["sUser" : nm, "sPass" : ps, "sToken": "''"]
self.mng.performRequest(URLString: accountBaseURL, parameters: params, method: .get, successCallback: { (dict) in
self.User.ID = dict.value(forKey: "ID") as! Int
self.User.LOGINNAME = dict.value(forKey: "LOGINNAME") as! String
self.User.PASSHASH = dict.value(forKey: "PASSHASH") as! String
completionHandler(dict, nil)
}) { (error) in
print("Error")
completionHandler(nil, error)
}
}
------ change in calling of the method -
@IBAction func signInButton(_ sender: roundButton) {
if(usernameTextField.text != "" && passwordTextField.text != "") {
self.viewModel.validate(nameField: self.usernameTextField.text!, passField: self.passwordTextField.text!) { (resultDict, error) in
if error != nil {
print("Error")
} else {
print("success")
self.presentPage(identifier: "homePageNavCtrl")
}
}
}
Upvotes: 2