Design Approach for Subclassing UITextField with d

2020-04-15 15:31发布

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??

1条回答
走好不送
2楼-- · 2020-04-15 16:03

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.

查看更多
登录 后发表回答