0

I am working on a login view and trying to change the border color of a UITextField in Xcode/swift3 when validation of the textfield fails. The UITextField should get a red border color.

The problem is that if enter an email, then a password and then press the submit button, i have to focus email text field again before it gets a red border.

This is my LoginViewController.swift so far:

import Foundation
import UIKit

class LoginViewController : UIViewController, UITextFieldDelegate  {

@IBOutlet weak var userEmailTextField: UITextField!
@IBOutlet weak var userPasswordTextField: UITextField!
override func viewDidLoad() {

    super.viewDidLoad()

}

// login button action
@IBAction func loginButtonTabbed(_ sender: Any) {

    // getting values from text fields
    let userEmail = userEmailTextField.text;
    let userPassword = userPasswordTextField.text;

    // set enpoind data
    let requestURL = NSURL(string: Constants.apiUrl)

    //creating a task to send the post request
    var request = URLRequest(url: requestURL as! URL)

    request.httpMethod = "POST"

    let postString = "cmd=addUser&email="+userEmail!+"&password="+userPassword!

    request.httpBody = postString.data(using: .utf8)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in

        guard let data = data, error == nil else {                                                 // check for fundamental networking error
            print("error=\(error)")
            return
        }

        if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {           // check for http errors
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }

        do {

            let json = try? JSONSerialization.jsonObject(with: data, options: [])

            // store json response to dictionary
            if let dictionary = json as? [String: Any] {

                // check if we got validation errors
                if let nestedDictionary = dictionary["validation"] as? [String: Any] {

                    // display validation messages on device
                    if let emailMsg = nestedDictionary["Email"] as? String {                            // change color of textfield
                        self.userEmailTextField.errorField()


                    }

                }

            }

        } catch let error as NSError {
            print(error)
        }

    }
    //executing the task
    task.resume()

}

}

and the UITextField extension UITextField.swift:

import Foundation
import UIKit

extension UITextField {

    func errorField(){
        self.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
        self.layer.borderWidth = 1.0;
    }

}
Yolo
  • 1,569
  • 1
  • 11
  • 16
  • " i have to focus email text field again before it gets a red border." <-- What?! It's unclear to me what you want... – mfaani Jul 04 '17 at 20:26
  • When launching the simulator and i type in an email in the email text field and a password in the password field, while the latter is focused and then tabbing submit button nothing happens. I have to tab the email field again before the border color is turned red. I want it to get red when tabbing submit if there is a validation error. – Yolo Jul 04 '17 at 20:34
  • Are you implementing any of the textFields delegate methods? If you are then also show their code. Otherwise this should have worked... – mfaani Jul 04 '17 at 21:11
  • No. Not that i know of :) i edited the post to include the entire view controller. I got nothing more than that. – Yolo Jul 04 '17 at 21:20
  • Hah! just figured out the issue! When you're doing a network call, it always happens in the background...so in order to do any kind of UI updates you need to be on the main queue. Just put the `self.userEmailTextField.errorField()` inside `DispatchQueue.main.async {...}` so it would be done immediately – mfaani Jul 04 '17 at 21:33
  • omg. Life saver! Thank you very much. It worked. Post it as an answer so i can accept it. – Yolo Jul 04 '17 at 21:44
  • But some other issues with your code...it's better not to extend a class directly. Why? See [here](https://stackoverflow.com/questions/41706504/why-should-not-directly-extend-uiview-or-uiviewcontroller). Also your function's name 'errorField' isn't a good choice. I would go for `chaneBorderColor(to:)` instead. `errorField` looks more like a name for an error textField – mfaani Jul 04 '17 at 23:27
  • See [Why does it take such a long time for UI to be updated from background thread?](https://stackoverflow.com/questions/44931759/why-does-it-take-such-a-long-time-for-ui-to-be-updated-from-background-thread) – mfaani Jul 05 '17 at 17:33
  • Interesting and diverse discussion ;) – Yolo Jul 05 '17 at 20:09

1 Answers1

1

When you're doing a network call, it always happens in the background...so in order to do any kind of UI updates you need to be on the main queue. Just put the self.userEmailTextField.errorField() inside DispatchQueue.main.async {...} so it would be done immediately.

Also haven't really tested your code very well. Why?

Even in your current code the border would still turn red, but it turns red after almost like 6-7 seconds (it could take less or more for you)...because it's being ran from background thread.

What I don't understand is why clicking on the textField again brings the red border right away!? Here's what I'm guessing happens:

From the background thread you update the model ie change the textField color which queues the UI/view to be updated...but since we're on a background queue, that UI updated could take a few seconds to happen

But then you tapped on the textField right away and forced a super quick read of the textField and all its properties which includes the border—from main thread (actual user touches are always handled through main thread)...which even though are not yet red on the screen, but since it's red on the model it will read from it and change color to red immediately.

mfaani
  • 33,269
  • 19
  • 164
  • 293