0

I want to check when the user starts editing a text field. There is a great clear answer on how to do that here.

However, in my case my textField is within a UITableview that's set up as its own class. I've tried lots of different ways to get this to work, but I keep getting the crash "libc++abi.dylib: terminating with uncaught exception of type NSException" I put a break in the textFieldDidChange func and it never gets called so the problem seems to be with how I'm calling that func from the target.

class TextFieldCell: UITableViewCell {

    lazy var textField: UITextField = {
        let tf = UITextField()
        tf.translatesAutoresizingMaskIntoConstraints = false
        tf.textAlignment = .center
        tf.textColor = .black
        tf.font = UIFont.systemFont(ofSize: 17)
        tf.clearButtonMode = .whileEditing
        return tf
    }()

    // For simplicity, the rest of the Cell setup not shown.
    // Adds target in AirInput VC to fire method when editing happens

    textField.addTarget(self, action: #selector(AirInputViewController.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)

}


class AirInputViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {

    @objc func textFieldDidChange(_ textField: UITextField) {

    }

}

I also tried the following for the target and it crashes as well.

textField.addTarget(AirInputViewController.self, action: #selector(AirInputViewController.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)

It feels like I'm missing something simple, but I have no idea what that simple fix is. Or perhaps should I add the target in the AirInputViewContoller? If so, how would I access the UITableViewCells where the Text Field is? Thanks!

Ben
  • 3,346
  • 6
  • 32
  • 51
  • Usually there is a pointer to what caused the exception before "terminating with uncaught exception of type NSException.". Would you mind posting the whole crash? – regina_fallangi Mar 10 '19 at 19:10
  • Do you mean this? "libc++abi.dylib: terminating with uncaught exception of type NSException" – Ben Mar 10 '19 at 19:19
  • No, usually the output is waaaay longer. I have a hunch of what can be happening. Posted an answer. If it's not the case, I can just delete it. – regina_fallangi Mar 10 '19 at 19:21

1 Answers1

1

Probably your crash is due to the fact that you do:

textField.addTarget(self, action: #selector(AirInputViewController.textFieldDidChange(_:)), for: UIControl.Event.editingChanged)

Here self is TextFieldCell, so I think it tries to go and check that AirInputViewController is inside TextFieldCell, which is not the case.

I would do:

class TextFieldCell: UITableViewCell {

    weak var delegate: TextFieldCellDelegate?

    lazy var textField: UITextField = {
        // same you have
    }()

    textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: UIControl.Event.editingChanged)

    @objc func textFieldDidChange(_ textField: UITextField) {
        delegate?.textFieldDidChange(textField)
    }

Create a fancy delegate:

protocol TextFieldCellDelegate: class {
     func textFieldDidChange(_ textField: UITextField)
}

class AirInputViewController: TextFieldCellDelegate {

    func textFieldDidChange(_ textField: UITextField) {
        // textField just changed!
    }

    // IMPORTANT! Set the delegate for the cell!

    func tableView(...cellForRow...) {
        let cell = ... as! TextFieldCell
        cell.delegate = self
        ...
        return cell
    }
}

Hope that helps.

regina_fallangi
  • 2,080
  • 2
  • 18
  • 38
  • I think I see where you are going, but can't quite get it to work. What is the purpose of the protocol? I get the error "objc can only be used with members of classes, @objc protocols, and concrete extensions of classes" when implementing it. – Ben Mar 10 '19 at 19:46
  • I am sorry, the protocol and its `textFieldDidChange` should have not been `@objc`. The purpose of the protocol is to create a delegate. The events inside the cell are then handled *within the cell* and one uses the `delegate` to inform the controller about the events the controller needs to know about, nothing else. Basically the event observer for a view should be inside that view, not in a different class. – regina_fallangi Mar 10 '19 at 19:50
  • I appreciate the help. Now without the @objc it gives the error "Protocol methods must not have bodies". – Ben Mar 10 '19 at 19:54
  • It means that I made the mistake of declaring the method inside the protocol as `func textFieldDidChange(_ textField: UITextField) {}`, meaning it had a body. Modified it to be only `func textFieldDidChange(_ textField: UITextField)` – regina_fallangi Mar 10 '19 at 19:55
  • 1
    That did the trick! Thanks so much for working through that with me and teaching me something new. Now I just need to stare at it for a while to internalize what's going on better. :) – Ben Mar 10 '19 at 20:19
  • 1
    The Delegate Pattern is very useful and spread in Swift, so I advise you to get familiar with it. Here some examples and explanations for it: https://stackoverflow.com/questions/7168714/what-is-the-purpose-of-a-delegation-pattern. The protocol here inherits from `class` so the variable can be `weak` and that way avoid a retention cycle because the controller has the view and the view would have a reference of the controller as the delegate. So by making the delegate weak, the view does not have a strong reference to the controller and there is no cycle. – regina_fallangi Mar 10 '19 at 20:24