2

I have subclassed the UITextField class so I can provide some built-in functionality for my application. I've changed the appearance to provide only an underline border for the UX design. Also, I want to use this control in situations where there is a picker (pick list, date / time picker). In these cases, I want to prevent editing BUT I still need to respond to touch events. To do this, I've added inspectable properties to control it from IB.

I can easily prevent the copy/paste menu from appearing by doing this:

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    if isFirstResponder && disableEditing {
        DispatchQueue.main.async(execute: {
            (sender as? UIMenuController)?.setMenuVisible(false, animated: false)
        })
        return false
    }

    return super.canPerformAction(action, withSender: sender)
}

However, I need to prevent them from typing or deleting characters in the textfield after something is selected in the picker. Typically, you would use the following delegate method:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return false
}

The question is how do you provide this kind of default behavior in a subclass? You could do a:

self.delegate = self

However, this leads to all sorts of shortcomings so it's not a good solution.

The other solution would be to implement a base UIViewController subclass (MyBaseViewController) but this would lead to convoluted and monolithic code later on.

It would be best if there was a clean way to provide this kind of default behavior in an encapsulated manner.

Obviously, there are a number of other ways to fix this (i.e. write the same code in 10 view controllers). Essentially, it seems there should be way to reuse delegate code when subclassing controls like this.

Anyone have any ideas??

Kamil Szostakowski
  • 2,153
  • 13
  • 19
Shawn
  • 406
  • 4
  • 12
  • What about adding a property to the subclass like, `hasSelectedOption`. You would set this to true/false depending on other actions to the control (like picking an option from your dropdown list), and then run different login in your subclass depending on the value of this property. I am understanding your question right? – Alex Oct 26 '17 at 16:08
  • @Alex not quite. I know how to control the logic based upon a property. I'm doing that already for controlling visual aspects of the control (underline border, color, etc.). However, the issue is how to handle the delegate logic specifically. The answer from Alex is exactly what I'm wrestling with. – Shawn Oct 26 '17 at 20:03

1 Answers1

6

Every approach you will take will be a trade-off. I don't think there is a perfectly clean solution for this type of problem. From my point of view, the best solution is to implement your custom UITextField as a kind of proxy delegate.

You can do it in two ways. This is the simplest one.

class CustomTextField: UITextField, UITextFieldDelegate
{
    var externalDelegate: UITextFieldDelegate?

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        delegate = self
    }

    func textFieldDidBeginEditing(_ textField: UITextField) {
        externalDelegate?.textFieldDidBeginEditing?(textField)
    }
}

If you don't want to modify the delegation interface for your custom control, you can do a small trick and overwrite the delegate property.

override var delegate: UITextFieldDelegate? {
    didSet {
        if delegate === self {
            return
        }            
        externalDelegate = delegate
        delegate = oldValue
    }
}

Pros

  • Works well when configured from code and from storyboard.
  • It's transparent from the client code perspective.

Cons

The drawback which comes with this approach is that you have to implement every method from the UITextFieldDelegate protocol in your UITextField subclass to fully support the delegation. Fortunately, there are only 8 of them and it's unlikely that you will need all, so you can narrow it down to the required subset.

Kamil Szostakowski
  • 2,153
  • 13
  • 19
  • That's a good suggestion. I did come across a multicast delegate and thought that might be a way to go. However, I especially like your approach that makes it transparent from the client code perspective. Thanks for taking the time to provide an answer. – Shawn Oct 26 '17 at 20:00
  • A very good answer(during my half-day search) for this topic. – inexcii Jun 24 '20 at 14:24
  • Only one little attention to remind, in the first way, that if the outside object(i.e. UIViewController) grabs the delegate (like `customTextField.delegate = self`) unintentionally, `CustomTextField` won't get delegate-calling anymore. – inexcii Jun 24 '20 at 14:31