Search code examples
iosarraysuitableviewkeyboarduitextfield

How to manage keyboard when there are UITextFields in UITableView using swift?


I am trying to find a way to avoid keyboard to not block selected UITextField in dynamic UITableViews.

I am aware this is a common issue and there are a lot of answers to this. However, answers I found are either in Obj-C or not involving dynamic UITableView.

My table view being has its own UITableViewCell so UITextFields are connected to it.

Also, I want to refresh the cell when UITextField entry has been made. I can pass that info (I am guessing cell's IndexPath.row?) to the UIViewController and have it listen to change and apply reload cell method?

Here is the related code I have:

class favCell: UITableViewCell, UITextFieldDelegate {

    @IBOutlet weak var deciPadField: UITextField!

    @objc func doneClicked(){
        updateTotalWorth()
        deciPadField.resignFirstResponder()
        deciPadField.endEditing(true)
    }

    func textFieldDidBeginEditing(_ textField: UITextField) {
        deciPadField.becomeFirstResponder()
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let currentString: NSString = (textField.text ?? "") as NSString
        let newString = currentString.replacingCharacters(in: range, with: string)
        return  newString.count <= 10
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        updateTotalWorth()
        deciPadField.resignFirstResponder()
        return true
    }

    private func textViewDidEndEditing(_ textView: UITextView) {
        updateTotalWorth()
        deciPadField.endEditing(true)
    }
}

UPDATE:

If I can access my ViewControllers view from UITableViewCell, below code will do the trick:

func animateTextField(textField: UITextField, up: Bool) {
    let movementDistance:CGFloat = -130
    let movementDuration: Double = 0.3
    var movement:CGFloat = 0

    if up {
        movement = movementDistance
    } else {
        movement = -movementDistance
    }
    UIView.beginAnimations("animateTextField", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(movementDuration)

    // Below line giving error, could not find the view. My ListVC.view should be referred to.
    self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)

    UIView.commitAnimations()
}


func textFieldDidBeginEditing(textField: UITextField) {
    self.animateTextField(textField: textField, up:true)
}

func textFieldDidEndEditing(textField: UITextField) {
    self.animateTextField(textField: textField, up:false)
}

Thank you!


Solution

  • Well I finally figured out how to solve this. I implemented this answer: https://stackoverflow.com/a/41040630/4441676

    Here is the solved code:

    Inside ViewController's ViewDidLoad added two observers as below:

    // Notification Observers
        NotificationCenter.default.addObserver(self, selector: #selector(ListVC.keyboardWillShow(notification:)), name: Notification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(ListVC.keyboardWillHide(notification:)), name: Notification.Name.UIKeyboardDidHide, object: nil)
    

    And added related two functions:

    @objc func keyboardWillShow(notification: Notification) {
        if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
            tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
        }
    }
    
    @objc func keyboardWillHide(notification: Notification) {
        UIView.animate(withDuration: 0.2, animations: {
            // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
            self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
        })
    }
    

    In the UITableViewCell where UITextFields are connected, simply implement UITextField Delegate and add related Notification.posts when keyboard will show/hide:

    deciPadField.delegate = self
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        NotificationCenter.default.post(name: Notification.Name.UIKeyboardDidShow, object: self)
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        NotificationCenter.default.post(name: Notification.Name.UIKeyboardDidHide, object: self)
    }
    

    I have inputAccessoryView, so dismissing keyboard handled via following:

    // Decipad config for adding Done button above itself
    let toolBar = UIToolbar()
    toolBar.sizeToFit()
    let flexiableSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
    let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneClicked))
    toolBar.setItems([flexiableSpace, doneButton], animated: false)
    
    @objc func doneClicked(){
        updateTotalWorth()
        deciPadField.resignFirstResponder()
        deciPadField.endEditing(true)
    }
    

    Hope this would help for someone else.