newToStackOverflow
newToStackOverflow

Reputation: 25

TableViewController / Custom cell - can't move to next text field

I have a table view controller with a custom cell which contains a text field - it's a form basically.

i want to automatically go to the next text field when users press "return" on their keyboard but for some reason my solution doesn't work.

In TableViewController, I do:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomCell
    cell?.box.tag = indexPath.row

In my custom table view cell, I have

override func awakeFromNib() {
    super.awakeFromNib()
    box.delegate = self
    ...
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    if let nextField = textField.superview?.viewWithTag(textField.tag+1) as? UITextField {
        nextField.becomeFirstResponder()
    } else {
        textField.resignFirstResponder()
    }
    return true
}

The issue is that textField.superview?.viewWithTag(textField.tag+1) is always nil. I don't know why because I clearly set the tag and also mark it as a delegate. thank you.

Upvotes: 1

Views: 185

Answers (2)

Ashish Bansal
Ashish Bansal

Reputation: 106

Adding some clarity and more suggestions to the valid answer by @jawadAli, as I feel you are still new to iOS development.

You are trying to get the tableView from the textField. But you will not get it by referring to the superview of textField. Because the view hierarchy would be like this:

UITableView > UITableViewCell > contentView > Your text field.

There can also be some more views in the view hierarchy, so you need to keep traversing through the superview chain till you get the UITableView. And @jawadAli has posted the code on how to get it.

But overall that is an incorrect approach. You should use delegation. I.e. your cell should call a method when it has resigned as first responder. And your table view controller will receive that call.

Then your view controller has to get the next cell and make it the first responder.

And if this doesn't make any sense to you, then I would very strongly suggest that you learn about Delegation. It's ubiquitous in iOS' libraries.

EDIT:

Approach to use delegation.

Create a protocol, let's say CellDelegate that has a function like func didFinishDataCapture(forCell: UITableViewCell).

The cell will have a delegate property of type CellDelegate.

The controller will conform to CellDelegate and will set itself as the cell's delegate in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

Now in your cell, when you are done with the text field (which you would know as cell would be the text field's delegate), you call your own delegate's function i.e. delegate.didFinishDataCapture(forCell: self).

In your implementation of didFinishDataCapture in the controller, you will know which cell has finished with the data capture and can put the logic on what to do next.

Upvotes: 1

Jawad Ali
Jawad Ali

Reputation: 14397

It should be nil as textField.superview is your cell class ... and your cell class does not have the view with required Tag .. so it will return nil..

import UIKit
  extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        guard let view = self.superview as? T else {
            return self.superview?.lookForSuperviewOfType(type: type)
        }
        return view
    }
}

Get tableView through this extension like this

let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)

your function will become

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
    if let cell = tableView?.cellForRow(at: IndexPath(row: textField.tag+1, section: 0)) as? CustomCell {
        cell.box.becomeFirstResponder()
    } else {
         textField.resignFirstResponder()
    }
      return true
    }

Upvotes: 1

Related Questions