1

How do I go about allowing my login button to be clicked only if the username and password text fields have been filled in?

Ideally the login button would be grayed out and un-clickable, only becoming fully functional once all fields have been filled in.

I'm very new to iOS development, so I am also unsure if I'd need to do anything regarding the "UITextFieldDelegate", would that be an important aspect here?

import UIKit

class LoginController: UIViewController {

// Creating text fields
let emailTF: UITextField = {
    let e = UITextField()
    let attributedPlaceholder = NSAttributedString(string: "Email", attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])
    e.attributedPlaceholder = attributedPlaceholder
    e.textColor = .white
    e.keyboardType = UIKeyboardType.emailAddress
    e.setBottomBorder(backgroundColor: ORANGE_COLOR, borderColor: .white)

    return e
}()

let passwordTF: UITextField = {
    let p = UITextField()
    let attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])
    p.attributedPlaceholder = attributedPlaceholder
    p.textColor = .white
    p.setBottomBorder(backgroundColor: ORANGE_COLOR, borderColor: .white)
    p.isSecureTextEntry = true

    return p
}()



// Creating buttons
let loginButton: UIButton = {
    let l = UIButton(type: .system)
    l.setTitleColor(.white, for: .normal)
    l.setTitle("Log In", for: .normal)
    l.backgroundColor = UIColor.rgb(r: 89, g: 156, b: 120)
    l.layer.cornerRadius = 25


    return l
}()



override func viewDidLoad() {
    super.viewDidLoad()

    navigationController?.isNavigationBarHidden = true

    view.backgroundColor = ORANGE_COLOR

    setupTextFields()
    setupLoginButton()
}



override var preferredStatusBarStyle: UIStatusBarStyle{
    return .lightContent
}



// Text Field Setup Functions
fileprivate func setupTextFields() {
    setupEmailTF()
    setupPasswordTF()
}

fileprivate func setupEmailTF() {
    view.addSubview(emailTF)
    emailTF.anchors(top: nil, topPad: 0, bottom: nil, bottomPad: 0, left: view.leftAnchor, leftPad: 24, right: view.rightAnchor, rightPad: 24, height: 30, width: nil)
    emailTF.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}

fileprivate func setupPasswordTF() {
    view.addSubview(passwordTF)
    passwordTF.anchors(top: emailTF.bottomAnchor, topPad: CGFloat(TEXT_FIELD_SPACING), bottom: nil, bottomPad: 0, left: emailTF.leftAnchor, leftPad: 0, right: emailTF.rightAnchor, rightPad: 0, height: 30, width: nil)
}



// Button Setup Functions
fileprivate func setupLoginButton() {
   view.addSubview(loginButton)
    loginButton.anchors(top: passwordTF.bottomAnchor, topPad: CGFloat(TEXT_FIELD_SPACING * 2), bottom: nil, bottomPad: 0, left: passwordTF.leftAnchor, leftPad: 0, right: passwordTF.rightAnchor, rightPad: 0, height: 50, width: nil)
}

}
Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50
  • Use a [`UITextFieldDelegate`](https://developer.apple.com/documentation/uikit/uitextfielddelegate) to detect when the fields are changed and make determinations from there – MadProgrammer Aug 15 '18 at 03:20
  • I've been looking around and just saw that I can look at the `userTF.text!.isEmpty` property without having to do anything with the delegate. Is that a good alternative? –  Aug 15 '18 at 03:29
  • 1
    Depends, when would you us it? Yes, you can use it AFTER the user has tapped the button, but why not disable the button until you've validated the fields? – MadProgrammer Aug 15 '18 at 03:33
  • Thanks! Using the delegate method, how do I check if the text fields have input in them or not, and how do I disable a button? –  Aug 15 '18 at 03:45
  • [swift - validating UITextField](https://stackoverflow.com/questions/31495188/swift-validating-uitextfield) [`isEnabled`](https://developer.apple.com/documentation/uikit/uicontrol/1618217-isenabled) is a property of `UIControl` from which `UIButton` is derived – MadProgrammer Aug 15 '18 at 03:53
  • @MadProgrammer All the solutions posted here only start to work once the user has tapped into the text field and started editing. Is there a way to setup a "listener" that is always checking if the fields have text in them even before the user taps them? –  Aug 15 '18 at 15:05
  • No, not really - you just set the initial state when the view appears – MadProgrammer Aug 15 '18 at 19:52

3 Answers3

1

You should be handling this in editingChanged. You want it the changes to be reflected ideally every time the user makes a change in the textField. So change your UITextField definition like this:

let emailTF: UITextField = {
    let e = UITextField()
    let attributedPlaceholder = NSAttributedString(string: "Email", attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])
    e.attributedPlaceholder = attributedPlaceholder
    e.textColor = .white
    e.keyboardType = UIKeyboardType.emailAddress
    e.setBottomBorder(backgroundColor: ORANGE_COLOR, borderColor: .white)
    e.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged)  // <- Add this line
    return e
}()

let passwordTF: UITextField = {
    let p = UITextField()
    let attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])
    p.attributedPlaceholder = attributedPlaceholder
    p.textColor = .white
    p.setBottomBorder(backgroundColor: ORANGE_COLOR, borderColor: .white)
    p.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged) // <- Add this line
    p.isSecureTextEntry = true
    return p
}()


//Handle button changes in this method
@objc func editingChanged(_ sender: UITextField) {
    if !emailTF.text?.isEmpty || !passwordTF.text?.isEmpty {
        button.isEnabled = false
        //Do highlighting changes to show disabled state
    } else {
        button.isEnabled = true
        //Undo highlighting changes
    }
}

Edit: Since both textFields' would be empty in the begnning, i also suggest you disable the button in viewDidLoad.

func viewDidLoad() {
    super.viewDidLoad()

    //Do other viewDidLoad stuff
    button.isEnabled = false
}

Or you could implement the didBeginEditing delegate and set emailTF as first responder as soon as the view is loaded.

func viewDidLoad() {
    super.viewDidLoad()

   //Do other viewDidLoad stuff
    emailTF.becomeFirstResponder()
}

func textFieldDidBeginEditing(_ textField: UITextField) {
    if !emailTF.text?.isEmpty || !passwordTF.text?.isEmpty {
        button.isEnabled = false
        //Do highlighting changes to show disabled state
    } else {
        button.isEnabled = true
        //Undo highlighting changes
    }
}
Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50
  • @cabyambo you could have easily checked that yourself. Check it and let me know also :P – Rakesha Shastri Aug 15 '18 at 14:46
  • Unfortunately the code only works after the user has tapped one of the text fields. So I could technically hit the login button before touching the text fields and it would allow me to tap it –  Aug 15 '18 at 14:50
  • @cabyambo Just add `button.isEnabled = false` in `viewDidLoad` because the textFields' are always empty on loading? – Rakesha Shastri Aug 15 '18 at 14:52
  • That would likely work, but I'd like to have it done in a way that it is always "listening" to see if the text fields are filled in or not. I appreciate your help though! –  Aug 15 '18 at 14:58
  • @cabyambo Yes. This is both listening and the default setup. You can't listen to something which isn't saying anything. That is why you need the default setup in `viewDidLoad`. – Rakesha Shastri Aug 15 '18 at 15:00
  • @cabyambo Check the latest edit. They should satisfy what you are trying to achieve. – Rakesha Shastri Aug 15 '18 at 15:04
  • Thank you! My understanding of code is very limited, so I'm just wondering if this is the only way to do it? Is there no way to have a "listener" that is always checking to see if the text fields contain anything, even before the user starts to edit them? –  Aug 15 '18 at 15:07
  • @cabyambo No the delegates are the listeners. So unless the the textFields' say anything, you cannot listen to them. Or you could have a `Timer` which would check the textfields for a specified duration which is a **very bad approach** – Rakesha Shastri Aug 15 '18 at 15:09
0

Here is a simple way to do this:
1. Listen delegate textFieldDidBeginEditing:
2. Inside this method you check length of emailTF and passwordTF, if both of them have length > 0, then enable button, else disable it.

  • This will only work once I start editing the fields though. If I just launch the app and do nothing the login button will be clickable. –  Aug 15 '18 at 15:02
0

Try to implement this code, hope this helps.

override func viewDidLoad() {
super.viewDidLoad()

navigationController?.isNavigationBarHidden = true

view.backgroundColor = ORANGE_COLOR

setupTextFields()
setupLoginButton()

emailTF.delegate = self
passwordTF.delegate = self

}

//Textfield delegate
func textFieldDidEndEditing(textField: UITextField) {
    if emailTF.text?.count > 0 && passwordTF.text?.count >0{
        loginButton.enabled = true
        loginButton.alpha = 1.0
    } else {
loginButton.enabled = false
loginButton.alpha = 0.5
}
}

// Button Setup Functions
fileprivate func setupLoginButton() {
view.addSubview(loginButton)
loginButton.anchors(top: passwordTF.bottomAnchor, topPad: 
CGFloat(TEXT_FIELD_SPACING * 2), bottom: nil, bottomPad: 0, left: 
passwordTF.leftAnchor, leftPad: 0, right: passwordTF.rightAnchor, rightPad: 
0, height: 50, width: nil)
loginButton.enabled = false
loginButton.alpha = 0.5
}
Bmacin
  • 235
  • 1
  • 12
  • This only works after I've finished editing the text fields. So the login button is fair game so to speak until I've finished editing at least one of the text fields. –  Aug 15 '18 at 15:01