0

I'm somewhat new to Swift and have mostly figured out how to use completion handlers with the help of this site. After a couple of days trying to get this to work, I'd appreciate more direct help.

I have a:

@IBAction func submitRegistrationButton(_ sender: Any) {

    if((firstNameField.text?.trimmingCharacters(in: .whitespacesAndNewlines).characters.count) == 0){
        showXAlert(title: "Oops!", message: "Please enter your first name.", viewController: self)
    }else if((lastNameField.text?.trimmingCharacters(in: .whitespacesAndNewlines).characters.count) == 0){
            showXAlert(title: "Oops!", message: "Please enter your last name.", viewController: self)
        }else if((emailAddressField.text?.trimmingCharacters(in: .whitespacesAndNewlines).characters.count) == 0){
                showXAlert(title: "Oops!", message: "Please enter your email address.", viewController: self)
            }else if !isValidEmail(testStr: emailAddressField.text!){
                    showXAlert(title: "Oops!", message: "Please enter a valid email address.", viewController: self)
                }else if((passwordField.text?.trimmingCharacters(in: .whitespacesAndNewlines).characters.count) == 0){
                    showXAlert(title: "Oops!", message: "Please enter a password.", viewController: self)
                    }else if passwordField.text != passwordConfirmationField.text{
                        showXAlert(title: "Oops!", message: "Your password and password confirmation do not match. Please correct.", viewController: self)
                }else{
                    registrant.firstName = firstNameField.text!
                    registrant.lastName = lastNameField.text!
                    registrant.zipCode = zipCodeField.text!
                    registrant.emailAddress = emailAddressField.text!
                    registrant.password = passwordField.text!
                    storeRegistrationInfo(registrant: registrant) { (object: XUserAPIResult) in

                        print("submissionStatus = \(object.success)")

                        if object.success == 1 {
                            showXAlert(title: "Welcome \(self.registrant.firstName)!", message: "Your registration was submitted successfully. Log in by clicking the Login button below", viewController: self)
                        }else{
                        showXAlert(title: "Un Oh", message: "There was a problem with your registration. \(object.errorMessage)", viewController: self)
                        }

                    }
                }
}

...that calls:

func storeRegistrationInfo(registrant: XRegistrantInfo, finished: @escaping (XUserAPIResult)->()) {
    var apiResult = XUserAPIResult()

    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    let context = appDelegate.persistentContainer.viewContext

    let newRegistrant = NSEntityDescription.insertNewObject(forEntityName: "User", into: context)

    let requestURL = NSURL(string: USER_API_URL)
    let request = NSMutableURLRequest(url: requestURL! as URL)

    request.httpMethod = "POST"

    newRegistrant.setValue(registrant.firstName, forKey: "firstName")
    newRegistrant.setValue(registrant.lastName, forKey: "lastName")
    newRegistrant.setValue(registrant.zipCode, forKey: "zipCode")
    newRegistrant.setValue(registrant.emailAddress, forKey: "emailAddress")
    newRegistrant.setValue(registrant.password, forKey: "password")
    newRegistrant.setValue(registrant.personna, forKey: "personna")
    do{
        try context.save()
        let postParameters = "tag=" + REGISTRATION_API_TAG + "&firstName=" + registrant.firstName + "&lastName=" + registrant.lastName + "&password=" + registrant.password + "&username=" + registrant.emailAddress + "&personna=" + registrant.personna + "&zipCode=" + registrant.zipCode
        request.httpBody = postParameters.data(using: .utf8)
        let task = URLSession.shared.dataTask(with: request as URLRequest){
            data, response, error in

            if error != nil{
                print("error is \(error)")
                return
            }

            print("response = \(response)")
            //parsing the response
            do {
                //converting resonse to NSDictionary
                let myJSON =  try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary

                //parse json 2

                if let dictionary = myJSON as? [String: Any]{
                    if let apiSuccess = dictionary["success"] as? Int{
                        apiResult.success = apiSuccess
                    }
                    if let apiError = dictionary["error"] as? Int{
                        apiResult.error = apiError
                        if apiError != 0{
                            if let apiErrorMessage = dictionary["error_msg"] as? String{
                                apiResult.errorMessage = apiErrorMessage
                            }

                        }else{
                            if let apiUID = dictionary["uid"] as? String{
                                apiResult.uniqueID = apiUID
                            }
                            if let nestedDictionary = dictionary["user"] as? [String: Any]{
                                if let apiFirstName = nestedDictionary["firstName"] as? String{
                                    apiResult.user.firstName = apiFirstName
                                }
                                if let apiLastName = nestedDictionary["lastName"] as? String{
                                    apiResult.user.lastName = apiLastName
                                }
                                if let apiEmail = nestedDictionary["e-mail"] as? String{
                                    apiResult.user.emailAddress = apiEmail
                                }
                                if let apiCreatedAt = nestedDictionary["created_at"] as? String{
                                    apiResult.user.createdAt = apiCreatedAt
                                }
                                if let apiPersonna = nestedDictionary["personna"] as? String{
                                    apiResult.user.personna = apiPersonna
                                }
                            }
                            finished(apiResult)
                        }
                    }
                }
            } catch {
                print(error)
            }
        }
        task.resume()
        finished(apiResult)
    } catch {
        print("There was an error saving to Core Data")
        finished(apiResult)
    }
}

The submitRegistrationButton() code is supposed to wait until storeRegistrationInfo() returns a XUserAPIResult struct and then display the appropriate alert based on XUserAPIResult's success property.

The problem is that the success-checking code is being executed both before storeRegistrationInfo() completes parsing the JSON; displaying the wrong alert, and then executed correctly after the JSON is parsed. The other aspects of the code (the web API call, parsing the JSON, saving the data to the web database) works.

I'm pretty sure there is something wrong with how I'm using the completion handler or calling storeRegistrationInfo() but I'm not sure exactly how to fix it.

How do I make sure that the alert code in @IBAction func submitRegistrationButton(_ sender: Any):

storeRegistrationInfo(registrant: registrant) { (object: XUserAPIResult) in

   print("submissionStatus = \(object.success)")

   if object.success == 1 {
      showXAlert(title: "Welcome \(self.registrant.firstName)!", message: "Your registration was submitted successfully. Log in by clicking the Login button below", viewController: self)
   }else{
      showXAlert(title: "Un Oh", message: "There was a problem with your registration. \(object.errorMessage)", viewController: self)
   }    
}

...is only called after the JSON is parsed and the UserAPIResult struct is populated and passed back?

Thanks.

Wayne
  • 3
  • 3

2 Answers2

0

try the following

typealias CompletionHandler = (data:XRegistrantInfo,success:Bool) -> Void;


func storeRegistrationInfo(registrant: XRegistrantInfo,completionHandler: CompletionHandler) {
    // your  code.
    //
    //
    completionHandler(data: dataToReturn,success : true/false)
}

And the call will be as

storeRegistrationInfo(registrant, { (data,success) -> Void in
    //onCompletion the code will go here
    if success {
        // success
    } else {
        // fail
    }
})
Moin
  • 121
  • 3
  • Thanks Moin. I just did some quick research on `typealias`. Are you suggesting this to improve maintainability/readability of the code? Is there any reason why you are checking a `success: Bool` versus, as I currently have it, the `success: Int` property of an XUserAPIResult instance? – Wayne Feb 03 '17 at 18:29
  • if the return type is just gonna switch between 2 values then I usually prefer using bool, and if its more than than but in a sequence then enum – Moin Feb 09 '17 at 13:30
0

The problem in your code is the line here:

task.resume()
finished(apiResult)

You should remove the call to the finished completion handler, since that one should be called in the placed where it is already placed, after getting the response.

Another improvement suggestion to you would be to simplify your field validation code to use guard statements.

Oleg Danu
  • 4,149
  • 4
  • 29
  • 47
  • Thanks! That seems to have fixed it! I will follow your advise regarding guard statements... – Wayne Feb 03 '17 at 16:18
  • ...I say "seems" above because the app is now crashing after the "Welcome..." alert is displayed. I don't know if this is related though. – Wayne Feb 03 '17 at 18:09
  • To close out this question and for anyone else who encounters the same problem, thanks to [this thread](http://stackoverflow.com/questions/37801370/how-do-i-dispatch-sync-dispatch-async-dispatch-after-etc-in-swift-3) I realized that `showXAlert()` was trying to present the alert from a thread other than the main one. This is what was causing the crash. – Wayne Feb 03 '17 at 19:50