0

I am making an iOS app using SwiftUI that requires login. When the create account button is pressed, the action triggers a function in my NetworkManager class that sends the inputed email and password as a post request and receives the appropriate data back for authentication. It then uses the received data to determine whether the credentials are valid

My issue is that it runs the code that verifies the inputed credentials against the API response before the response is actually received. Consequently, the result is the same each time.

Button(action: {


    self.networkManager.signUp(email: self.inputEmail, password: self.inputPassword)

    // These if statements run before the above line is executed
    if self.networkManager.signUpResponse.code == nil {
        // SUCCESSFUL REGISTRATION
        ProgressHUD.showSuccess("Account Successfully Created!")
        UserDefaults.standard.set(true, forKey: "LoggedIn")
        self.showingWelcomePage = false
    }

    if self.networkManager.signUpResponse.code == 201 {
        // REGISTRATION FAILED
        ProgressHUD.showError("This account already exists", interaction: false)
    }

}) {

    Text("Create Account")
        .font(.headline)
}

I have tried using DispatchQueue.main.async() or creating my own thread, however nothing seems to work. I need to find a way to pause the main thread in order to wait for this line of code to execute before proceeding without using DispatchQueue.main.sync() as this results in deadlock and program crash.

Here is the code for the function that makes the post request to the API

class NetworkManager: ObservableObject {

    @Published var signUpResponse = AccountResults()

    func signUp(email: String, password: String) {

        if let url = URL(string: SignUpAPI) {
            let session = URLSession.shared

            let bodyData = ["school": "1",
                            "email": email,
                            "password": password]

            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)

            let task = session.dataTask(with: request) { (data, response, error) in
                if error == nil {
                    let decoder = JSONDecoder()
                    if let safeData = data {
                        do {
                            let results = try decoder.decode(AccountResults.self, from: safeData)

                            DispatchQueue.main.async {

                                self.signUpResponse = results

                            }
                        } catch {
                            print(error)
                        }
                    }
                }

            }
            task.resume()
        }
    }
}

Thanks in advance!

  • 1
    Don't try to force an asynchronous task to become synchronous. I'm sure that `networkManager` provides an API with a completion handler. Use that. – vadian Dec 18 '19 at 15:35
  • @vadian so I should instead only run the if statements after it receives the completion handler? – Arían Taherzadeh Dec 18 '19 at 15:37
  • 1
    Yes, a completion handler is a closure which is called asynchronously. I don't know this particular `NetworkManager` library. – vadian Dec 18 '19 at 15:39
  • It's just what I called my class that pulls from the API. I will attach the code in the question – Arían Taherzadeh Dec 18 '19 at 15:44
  • Please search for completion handler, there are hundreds of examples, one of them is https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function – vadian Dec 18 '19 at 16:10
  • 2
    Also, you shouldn't have procedural code like that in your button action. SwiftUI is declarative. Logic regarding the success or failure of the login should be in the model. The view should simply bind to the appropriate properties in your model. When the login succeeds or fails the model is updated and the observing views are updated due to the binding. – Paulw11 Dec 18 '19 at 19:20
  • NetworkManager should have some sort of completion block you can use, that will wait until the response is received to execute. – Tofu Warrior Dec 18 '19 at 20:31

1 Answers1

0

Try it:

func signUp(email: String, password: String, completion: @escaping((Error?, YourResponse?) -> Void)) {

        if let url = URL(string: SignUpAPI) {
            let session = URLSession.shared

            let bodyData = ["school": "1",
                            "email": email,
                            "password": password]

            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)

            let task = session.dataTask(with: request) { (data, response, error) in
                if error == nil {
                    let decoder = JSONDecoder()
                    if let safeData = data {
                        do {
                            let results = try decoder.decode(AccountResults.self, from: safeData)

                            DispatchQueue.main.async {

                                self.signUpResponse = results
                                completion(nil, results)
                            }
                        } catch {
                            print(error)
                            completion(error, nil)
                        }
                    }
                }

            }
            task.resume()
        }
    }
}

use escaping in your function, I think will get exactly the point server response data or get errors too. my english is so bad.

quynhbkhn
  • 59
  • 4
  • What would I add to my function call in the button action to make this work? – Arían Taherzadeh Dec 19 '19 at 16:15
  • Xcode will suggest prototype func for you, like that: api.signUp(email: "abc", password: "aaa") { (error, response) in if error != nil { print("error = \(error)") } else { print("response = \(response)") } } – quynhbkhn Dec 20 '19 at 09:13