仅当所有文本字段都已填写时才在 Swift 中启用按钮

Posted

技术标签:

【中文标题】仅当所有文本字段都已填写时才在 Swift 中启用按钮【英文标题】:Enable a button in Swift only if all text fields have been filled out 【发布时间】:2016-01-22 07:17:44 【问题描述】:

我无法弄清楚如何更改我的代码,以便在填写我的三个文本字段时启用导航栏中的“完成”按钮。

我目前有三个 UITextField 和一个 UIButtonItem。 habitNameField 和goalField 都是手动文本字段,frequencyField 是Picker View。

@IBOutlet weak var habitNameField: UITextField!
@IBOutlet weak var goalField: UITextField!
@IBOutlet weak var frequencyField: UITextField!

@IBOutlet weak var doneBarButton: UIBarButtonItem!

当在第一个字段中输入内容时,我还有以下功能。

func textField(habitNameField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 
    let oldHabitNameText: NSString = habitNameField.text!
    let newHabitNameText: NSString = oldHabitNameText.stringByReplacingCharactersInRange(range, withString: string)
    doneBarButton.enabled = (newHabitNameText.length != 0)
    return true

我尝试更改代码,使其将其他两个字段作为参数,并仅在填写所有三个字段时才启用 doneBarButton。

func textField(habitNameField: UITextField, goalField: UITextField, frequencyField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 
    let habitNameText: NSString = (habitNameField.text!).stringByReplacingCharactersInRange(range, withString: string)
    let goalText: NSString = (goalField.text!).stringByReplacingCharactersInRange(range, withString: string)
    let frequencyText: NSString = (frequencyField.text!).stringByReplacingCharactersInRange(range, withString: string)

    doneBarButton.enabled = (habitNameText.length != 0) && (goalText.length != 0) && (frequencyText.length != 0)
    return true

但是,即使我填写了所有三个文本字段,它也不起作用。

我非常感谢任何帮助,并感谢任何提前做出贡献的人!

这里的所有代码:

class HabitDetailViewController: UITableViewController, UITextFieldDelegate, UIPickerViewDataSource,UIPickerViewDelegate 
@IBOutlet weak var habitNameField: UITextField!
@IBOutlet weak var goalField: UITextField!
@IBOutlet weak var doneBarButton: UIBarButtonItem!
@IBOutlet weak var frequencyField: UITextField!

var frequencies = ["Day", "Week", "Month", "Year"]
var frequencyPicker = UIPickerView()

var habitToEdit: HabitItem?
weak var delegate: HabitDetailViewControllerDelegate?

@IBAction func cancel() 
    delegate?.habitDetailViewControllerDidCancel(self)


@IBAction func done() 
    print("You plan to do \(habitNameField.text!) \(goalField.text!) times a \(frequencyField.text!.lowercaseString).")
    if let habit = habitToEdit 
        habit.name = habitNameField.text!
        habit.numberLeft = Int(goalField.text!)!
        habit.frequency = frequencyField.text!
        delegate?.habitDetailViewController(self, didFinishEditingHabit: habit)
     else 
        let habit = HabitItem()
        habit.name = habitNameField.text!
        habit.numberLeft = Int(goalField.text!)!
        habit.frequency = frequencyField.text!
        habit.completed = false
        delegate?.habitDetailViewController(self, didFinishAddingHabit: habit)
    


override func viewWillAppear(animated: Bool) 
    super.viewWillAppear(animated)
    habitNameField.becomeFirstResponder()
    frequencyPicker.hidden = false


override func viewDidLoad() 
    super.viewDidLoad()
    frequencyPicker.dataSource = self
    frequencyPicker.delegate = self
    doneBarButton.enabled = false
    habitNameField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
    goalField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
    frequencyField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
    frequencyField.inputView = frequencyPicker
    if let habit = habitToEdit 
        title = "Edit Item"
        habitNameField.text = habit.name
        goalField.text = String(habit.numberLeft)
        doneBarButton.enabled = true
    


override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? 
    return nil


func textField(habitNameField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 
    let oldHabitNameText: NSString = habitNameField.text!
    let newHabitNameText: NSString = oldHabitNameText.stringByReplacingCharactersInRange(range, withString: string)
    doneBarButton.enabled = (newHabitNameText.length != 0)
    return true


func checkFields(sender: UITextField) 
    sender.text = sender.text?.stringByTrimmingCharactersInSet(.whitespaceCharacterSet())
    guard
        let habit = habitNameField.text where !habit.isEmpty,
        let goal = goalField.text where !goal.isEmpty,
        let frequency = frequencyField.text where !frequency.isEmpty
        else  return 
    // enable your button if all conditions are met
    doneBarButton.enabled = true

【问题讨论】:

【参考方案1】:

Xcode 9 • Swift 4

您可以 addTarget 到您的文本字段以监视控制事件 .editingChanged 并为所有这些事件使用单个选择器方法:

override func viewDidLoad() 
    super.viewDidLoad()
    doneBarButton.isEnabled = false
    [habitNameField, goalField, frequencyField].forEach( $0.addTarget(self, action: #selector(editingChanged), for: .editingChanged) )

创建选择器方法并使用guard 结合where 子句(Swift 3/4 使用逗号)确保所有文本字段不为空,否则直接返回。 Swift 3 不需要@objc,但 Swift 4 需要:

@objc func editingChanged(_ textField: UITextField) 
    if textField.text?.characters.count == 1 
        if textField.text?.characters.first == " " 
            textField.text = ""
            return
        
    
    guard
        let habit = habitNameField.text, !habit.isEmpty,
        let goal = goalField.text, !goal.isEmpty,
        let frequency = frequencyField.text, !frequency.isEmpty
    else 
        doneBarButton.isEnabled = false
        return
    
    doneBarButton.isEnabled = true

sample

【讨论】:

这是有效的,但出于某种原因,即使只填写了部分字段,它也有效。这是为什么呢? 杜尔。这是我的菜鸟错误。谢谢你帮我抓住了。此外,频率的最终文本字段不起作用,因为它是一个选择器视图。有什么想法吗? 您可以创建一个布尔变量来监控它,并在用户设置它后对其进行标记。然后只需将其添加到保护语句中 如何检查电子邮件 id 并且单个文本字段不为空,尝试过但不工作 let emaiTextField = existingEmailAddressTextField.text, !emaiTextField.isEmpty, let validEmail = existingEmailAddressTextField.text, self.validateEmail(validEmail) ==真, 如何检查相同文本字段的有效电子邮件 ID【参考方案2】:

Swift 5.1 /Xcode 11

override func viewDidLoad() 
    super.viewDidLoad()
    setupAddTargetIsNotEmptyTextFields()        


func setupAddTargetIsNotEmptyTextFields() 
    okButton.isHidden = true //hidden okButton
    nameUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    emailUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    passwordUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    confimPasswordUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)        
   

然后创建选择器方法并使用guard

@objc func textFieldsIsNotEmpty(sender: UITextField) 

    sender.text = sender.text?.trimmingCharacters(in: .whitespaces)

    guard
      let name = nameUserTextField.text, !name.isEmpty,
      let email = emailUserTextField.text, !email.isEmpty,
      let password = passwordUserTextField.text, !password.isEmpty,
      let confirmPassword = confimPasswordUserTextField.text,
          password == confirmPassword          
      else
    
      self.okButton.isHidden = true
      return
    
    // enable okButton if all conditions are met
    okButton.isHidden = false
   

【讨论】:

应该是正确的答案,对我来说效果很好,委托方法不提供类似 .editingChanged 的​​东西 很好的解决方案!您需要将“textFieldsIsNotEmpty”标记为“@objc”,否则编译器会抛出错误。请更新答案。 如何检查是否有效的电子邮件 ID 并为相同的文本字段留空【参考方案3】:

最好的方法是在 ViewDidLoad 方法中添加观察者。不仅仅是检查 textField Delegate 方法是否所有 TextField 都已填满。一旦它填满调用 oberserver 方法,您只需要启用按钮。

注意:

您可以将观察者用于启用或禁用按钮

希望对你有所帮助。

【讨论】:

你能举例说明如何添加观察者吗?谢谢!【参考方案4】:

我继续将其抽象为一个帮助类,可以用于他们的 swift 项目。

import Foundation
import UIKit

class ButtonValidationHelper 

  var textFields: [UITextField]!
  var buttons: [UIButton]!

  init(textFields: [UITextField], buttons: [UIButton]) 

    self.textFields = textFields
    self.buttons = buttons

    attachTargetsToTextFields()
    disableButtons()
    checkForEmptyFields()
  

  //Attach editing changed listeners to all textfields passed in
  private func attachTargetsToTextFields() 
    for textfield in textFields
        textfield.addTarget(self, action: #selector(textFieldsIsNotEmpty), for: .editingChanged)
    
  

  @objc private func textFieldsIsNotEmpty(sender: UITextField) 
    sender.text = sender.text?.trimmingCharacters(in: .whitespaces)
    checkForEmptyFields()
  


  //Returns true if the field is empty, false if it not
  private func checkForEmptyFields() 

    for textField in textFields
        guard let textFieldVar = textField.text, !textFieldVar.isEmpty else 
            disableButtons()
            return
        
    
    enableButtons()
  

  private func enableButtons() 
    for button in buttons
        button.isEnabled = true
    
  

  private func disableButtons() 
    for button in buttons
        button.isEnabled = false
    
  


然后在您的视图控制器中只需简单地使用

初始化助手
buttonHelper = ButtonValidationHelper(textFields: [textfield1, textfield2, textfield3, textfield4], buttons: [button])

确保在顶部保留一个强引用以防止释放

var buttonHelper: ButtonValidationHelper!

【讨论】:

【参考方案5】:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool 
    if (textField == self.textField1)  /* do Something */ 
    else if (textField == self.textField2)  /* do Something */ 
    else if (textField == self.textField3)  /* do Something */ 

    // regardless of what you do before, doneBarButton is enabled when all are not empty
    doneBarButton.enabled = (textField1.length != 0) && (textField2.length != 0) && (textField3.length != 0)
    return true

【讨论】:

【参考方案6】:

为什么不将检查功能移到单独的函数中

func setDoneButtonStatus()

    let habitNameText: NSString = (habitNameField.text!).stringByReplacingCharactersInRange(range, withString: string)
    let goalText: NSString = (goalField.text!).stringByReplacingCharactersInRange(range, withString: string)
    let frequencyText: NSString = (frequencyField.text!).stringByReplacingCharactersInRange(range, withString: string)

    doneBarButton.enabled = (habitNameText.length != 0) && (goalText.length != 0) && (frequencyText.length != 0)

然后使用

func textFieldShouldReturn(textField: UITextField) -> Bool

    textField.resignFirstResponder()
    setDoneButtonStatus()

【讨论】:

【参考方案7】:

这对我有用:希望它有所帮助

  func textFieldDidEndEditing(textField: UITextField) 
        if txtField1.hasText() && textField2.hasText() && textField3.hasText()
            doneBarButton.enabled = true
        
    

【讨论】:

请注意,这仅适用于 ios(10.0 及更高版本)、tvOS(10.0 及更高版本)。顺便说一句,它不再是一种方法。现在它是一个计算属性【参考方案8】:

您可以创建文本字段数组[UITextField] 或插座集合。让我们将数组称为 textFields 或类似的名称。

doneBarButton.isEnabled = !textFields.flatMap  $0.text?.isEmpty .contains(true)

并在监控文本字段文本变化的方法中调用上面的代码。

【讨论】:

【参考方案9】:

使用 Combine (Xcode11+, iOS13+) 这变得更容易了。

首先,您需要能够为文本更改创建发布者:

extension UITextField 

    var textPublisher: AnyPublisher<String, Never> 
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: self)
            .compactMap  $0.object as? UITextField 
            .map  $0.text ?? "" 
            .eraseToAnyPublisher()
    


然后你可以组合来自多个文本字段的多个发布者:

private var readyToLogIn: AnyPublisher<Bool, Never> 
   return Publishers
      .CombineLatest(
         emailTextField.textPublisher, passwordTextField.textPublisher
      )
      .map  email, password in
         !email.isEmpty && !password.isEmpty
      
      .eraseToAnyPublisher()


然后根据组合的 Publisher 更改按钮的 isEnabled 属性

readyToLogIn
   .receive(on: RunLoop.main)
   .assign(to: \.isEnabled, on: signInButton)

【讨论】:

【参考方案10】:

Swift 5.0+

我会做这样的事情,它结合了已经给出的答案,但更加精简和最新:

在你的viewDidLoad()

    [usernameTextField, passwordTextField].forEach 
        $0?.addTarget(self,
                      action: #selector(editingChanged(_:)),
                      for: .editingChanged)
    

然后创建以下内容:

@objc func editingChanged(_ textField: UITextField) 
    
    // Trim whitespace and newlines
    textField.text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)
    
    // Assumes loginButton exists and there's a reference to it in the current scope.
    loginButton.isEnabled = ![usernameTextField, passwordTextField].compactMap 
        $0.text?.isEmpty
    .contains(true)

【讨论】:

【参考方案11】:

Xcode 10.2 • Swift 4.3 版本的 Leo Dabus 以上。

此解决方案用于添加用户,可能是最常见的实现 进行此类验证。

override func viewDidLoad() 
super.viewDidLoad()
addUserButton.backgroundColor = disabledButtonColor
addUserButton.isEnabled = false

[emailField, userNameField, firstNameField, lastNameField].forEach  (field) in
    field?.addTarget(self,
                     action: #selector(editingChanged(_:)),
                     for: .editingChanged)




@objc private func editingChanged(_ textField: UITextField) 
if textField.text?.count == 1 
    if textField.text?.first == " " 
        textField.text = ""
        return
    

guard
    let email       = emailField.text,      !email.isEmpty,
    let userName    = userNameField.text,   !userName.isEmpty,
    let firstName   = firstNameField.text,  !firstName.isEmpty,
    let lastName    = lastNameField.text,   !lastName.isEmpty
    else 
        addUserButton.isEnabled = false
        addUserButton.backgroundColor = disabledButtonColor
        return

addUserButton.isEnabled = true
addUserButton.backgroundColor = activeButtonColor

【讨论】:

以上是关于仅当所有文本字段都已填写时才在 Swift 中启用按钮的主要内容,如果未能解决你的问题,请参考以下文章

当所有文本字段都填写在uitableviewcell(SWIFT)中时如何启用按钮

SwiftUI:仅当输入不为空时才启用保存按钮

仅当 Validation 为 True 时才启用 Jbutton

仅当前面有 (\d2 -) 时才在 html 中查找链接 (href) [重复]

仅当条件为 True 时才在 python 中使用 Eel 调用 JavaScript 函数

如果文本字段被快速填写,则启用按钮