UITableView 中的 UITextField 没有成为第一响应者

Posted

技术标签:

【中文标题】UITableView 中的 UITextField 没有成为第一响应者【英文标题】:UITextField in UITableView not becoming first responder 【发布时间】:2017-06-22 19:27:46 【问题描述】:

我有UITableView,大约有 20 行,每行包含一个UITextField。我第一次点击文本字段时将打开键盘,我准备编辑此文本字段。如果我点击下一个文本字段(注意键盘一直显示),键盘仍然显示,但蓝色光标不在新文本字段中,我无法输入任何文本。但是,如果我再次点击另一个文本字段,它就可以正常工作。这种行为交替发生,一次有效,另一次无效。

委托方法textFieldShouldBeginEditing(_:) 总是被调用,不管我是否可以编辑。委托方法textFieldDidBeginEditing(_:)仅在编辑工作时调用。

这是cellForRowAt的代码

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: "TextFieldCell")!
    let titleLabel = cell.viewWithTag(1) as! UILabel
    let contentTextField = cell.viewWithTag(2) as! FixableTextField
    contentTextField.delegate = self
    contentTextField.inputAccessoryView = doneToolbar

    // Enable/disable editing for text fields
    if isEditing 
        contentTextField.enableEditing()
     else 
        contentTextField.disableEditing()
    

    // Present Profile Data
    if profileUpdateBuffer != nil 

        switch indexPath.row 
        case 0:
            titleLabel.text = "Count"
            contentTextField.text = "\(profileUpdateBuffer!.count)"
            contentTextField.purposeID = "count"
            contentTextField.keyboardType = .numberPad

        case 1:
            titleLabel.text = "City"
            contentTextField.text = "\(profileUpdateBuffer!.city)"
            contentTextField.purposeID = "city"
            contentTextField.keyboardType = .default

        // ...

        case 20:
            titleLabel.text = "Name"
            contentTextField.text = "\(profileUpdateBuffer!.name)"
            contentTextField.purposeID = "name"
            contentTextField.keyboardType = .default

        default:
            titleLabel.text = ""
            contentTextField.text = ""
        

        return cell
    

    // No data available -> show info in first row
    else 
        if indexPath.row == 0 
            titleLabel.text = "No data"
            contentTextField.text = "No data"
        
        else 
            titleLabel.text = ""
            contentTextField.text = ""
        
        return cell
    

enableEditing()disableEditing() 方法来自类 FixableTextField。我可以看到文本字段始终处于启用状态,因为我可以看到文本字段边框

// Extract from FixableTextField class
func enableEditing() 
    self.isEnabled = true
    self.borderStyle = .roundedRect


func disableEditing() 
    self.isEnabled = false
    self.borderStyle = .none

UITextField 的代码

func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool 

    // Delete empty field indicator "-"
    if textField.text == "-" 
        textField.text = ""
    

    //Move profileTable's contentView to correct position
    if textField is FixableTextField 
        let path = IndexPath(row: rowMap[(textField as! FixableTextField).purposeID]!, section: 0)
        moveContentViewUp(indexPath: path)
    
    return true




func textFieldDidEndEditing(_ textField: UITextField) 

    // Save new value to profileUpdateBuffer
    do 
        try self.profileUpdateBuffer?.setProperty(value: textField.text!, key: (textField as! FixableTextField).purposeID)
     catch ProfileError.PropertySettingWrongType 
        let falseInputAlert = UIAlertController(title: "False Input", message: "The input for this field is not valid.", preferredStyle: .alert)
        falseInputAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
        self.present(falseInputAlert, animated: true, completion: nil)
     catch 
        print("Error when trying to set property for profileUpdateBuffer in ProfileViewController")
    

    // Display new data in table
    profileTable.reloadData()

从类ProfileData 中的setProperty 方法中提取。 profileUpdateBuffer 的类型为 ProfileData

func setProperty(value:String, key:String) throws 
    switch key 
    case "count":
        count = value

    case "city":
        count = value

    // ...

    case "name":
        name = value

    default:
        throw ProfileError.PropertySettingWrongType
    

【问题讨论】:

请发布您的代码,以便我们为您提供帮助。特别是您的 cellForRowAt indexPath 函数。如何将侦听器添加到您的文本字段? 谢谢,我当然会的。 'textFieldShouldBeginEditing(_:)'中是否有过滤逻辑?我看到您还将您的 VC 设置为所有文本字段的代表。您是否有逻辑来区分委托方法实现中的单元格?您可以尝试在单元格中移动 TextField 委托,以便每个单元格自己负责。 您是否总是在您的textFieldShouldBeginEditing 中返回true。如果没有,你能发布代码吗? 想象一下这种情况:您有单元格 1 和单元格 2,它们各自的文本字段 1 (tf1) 和文本字段 2 (tf2)。您正在编辑 tf1.然后选择tf2。该框架将辞去 tf1 上的第一响应者并成为 tf2 上的第一响应者。这将触发 tf2 的 shouldBeginEditing。现在,响应 tf1 上的已辞职响应者,将使用 didEndEditing 调用 tf1 的代表。但是您正在编辑 tf2,而不是 tf1!此调用将有效地使 tf2 不可编辑。您可以在代码中添加一些日志并确认这是真的吗?如果为真,请尝试通过在单元格中移动 tf 委托来解决此问题。 【参考方案1】:

我制作了一个小程序来模仿您描述的行为。 看来问题是由textFieldDidEndEditing(_:) 末尾的表格视图数据重新加载引起的:

func textFieldDidEndEditing(_ textField: UITextField) 

    // Save new value to profileUpdateBuffer
    do 
        try self.profileUpdateBuffer?.setProperty(value: textField.text!, key: (textField as! FixableTextField).purposeID)
     catch ProfileError.PropertySettingWrongType 
        let falseInputAlert = UIAlertController(title: "False Input", message: "The input for this field is not valid.", preferredStyle: .alert)
        falseInputAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
        self.present(falseInputAlert, animated: true, completion: nil)
     catch 
        print("Error when trying to set property for profileUpdateBuffer in ProfileViewController")
    

    // Display new data in table
    profileTable.reloadData()

尝试删除profileTable.reloadData(),以便进行实验以确认问题的根本原因(是的,您的其他单元格将不会更新)。

解决此问题的一种方法是在 textFieldDidEndEditing(_:) 中使用 visibleCells 上的直接单元格更新。我看到profileUpdateBuffer? 是您的数据模型。如果它们位于表格视图的可见单元格属性中,只需从模型中手动更新单元格的 titleLabel 和 textField 属性即可。

如果您想相应地调整单元格的大小,请使用 AutoLayout 和 UITableViewAutomaticDimension 作为表格视图行高并结合 beginUpdates()/endUpdates() 调用。

有关如何在不丢失键盘焦点的情况下实现直接单元格操作和/或动态单元格大小更新的更多详细信息check the accepted answer on this question我已经回答了。

希望这会有所帮助!

【讨论】:

非常感谢您的详细回答。你是对的,当我删除 profileTable.reloadData() 时它工作得很好。正如你所说,我通过更新没有realodData 的单元格来修复它,现在一切正常。您是否解释了为什么在使用 reloadData 时会发生这种行为? 当您启动reloadData() 时,表格视图会自行禁用用户交互。然后,单元格将为 becomeFirstResponder() 返回 false。在您的情况下,第二个单元格不会成为第一响应者。如果您随后选择一个新单元格,则不会调用 reloadData(),因为前一个单元格没有成为第一响应者,因此不会调用 didEndEditing()(重新加载所在的位置)。这就是可以编辑第三个单元格的原因。我不知道为什么当编辑不可用时键盘仍然在屏幕上 - 我希望当之前的文本字段退出第一响应者时它会被关闭。 完美的解释。非常感谢!【参考方案2】:

我认为问题在于您调用 profileTable.reloadData()... 您可能应该只重新加载一个单元格。或许 textFieldDidEndEditing(:) 被调用时使用旧的 TextField 作为参数,然后是新的 TextField 的 textFieldShouldBeginEditing(:)。问题是您刷新了 textFieldDidEndEditing(:) 末尾的所有单元格,这意味着系统作为参数传递给 textFieldShouldBeginEditing(:) 的 TextField 可能与相应的单元格包含...可能是这些新的击键被发送到属于可重用单元格队列中的单元格的 TextField,即不可见,但仍存在于内存中的某处。

【讨论】:

以上是关于UITableView 中的 UITextField 没有成为第一响应者的主要内容,如果未能解决你的问题,请参考以下文章

水平 UITableView 中的垂直 UITableView

在 UITableView 中的 UINib 中重新加载 UITableView

尝试在用户向 iOS 中的 UITableView 添加行时动态增加 UITableView 的高度

UIViewController 中的 UITableView

UITableView 中的圆顶角

uiscrollview 中的 uitableview(滚动问题)