Search code examples
iosswiftuitableviewuitextfieldfirst-responder

UITextField in UITableView not becoming first responder


I have UITableView with about 20 row which each contain a UITextField. The first time I tap in a textfield will open the keyboard and I am ready to edit this textfield. If I tap on the next textfield (notice the keyboard is displayed all the time) the keyboard is still displayed but the blue cursor is not in the new textfield and I cannot enter any text. But if I tap on another textfield again, it works just fine. This behavior occurs alternately, one time it works the other time it doesn't.

The delegate method textFieldShouldBeginEditing(_:) is always called, wether I can edit or not. The delegate method textFieldDidBeginEditing(_:) is only called when editing works.

This is the code for 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
    }
}

The enableEditing() and disableEditing() method are from class FixableTextField. I can see that the textfields are always enabled because I can see the textfield border

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

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

Code for the 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()
}

Extract from setProperty method which is from class ProfileData. profileUpdateBuffer is of type 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
    }
}

Solution

  • I've made a small program to mimic the behavior you describe. It seems the issue is caused by table view data reloading at the end of your 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()
    }
    

    Try removing profileTable.reloadData() for the sake of experiment to confirm the root cause of the problem (yes, your other cells will not be updated).

    One way to solve this is by utilizing direct cell updates on visibleCells in textFieldDidEndEditing(_:). I see profileUpdateBuffer? is your data model. Just update your cell's titleLabel and textField properties manually from your model if they are in visible cells property of the table view.

    If you want to size the cells accordingly, use AutoLayout and UITableViewAutomaticDimension for table view row height combined with beginUpdates()/endUpdates() calls.

    For more details on how to achieve direct cell manipulation and/or dynamic cell size update without loosing the keyboard focus check the accepted answer on this question I've already answered.

    Hope this will help!