UITextField Обработка ввода


#1

Добрый день товарищи :slightly_smiling_face:
Возник такой вопрос, в интерфейсе UITextFieldDlegate есть прекрасный метод optional func textField( _ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
как я могу поступить, если я наследуюсь от UItextField, в нем у меня должно быть строго ограниченное количество символов, эта обработка происходит внутри класса, но при этом я не хочу использовать делегат внутри реализации UItextField

Кто сможет подсказать, в каких местах apple вызывает этот метод, что я такое могу оверрайднуть, чтобы добавить проверку до момента присваивания филду введенного символ-строки!?

По сути у нас UITextField реализует протокол UIKeyInput, но вот я скорее всего что-то не топонимию, буду признателен за совет)


#2

не совсем понял вопрос, но я так ограничивал textfield по количеству символов (4)

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

    let currentCharacterCount = textField.text?.count ?? 0
    if range.length + range.location > currentCharacterCount {
        return false
    }
    let newLength = currentCharacterCount + string.count - range.length
    return newLength <= 4
}

#3

ну да, это методе делегата, а хотелось бы без него) чтобы внутри класа не определять методы делегата, иначе у вас все сломается, если вы используете делегат во view)


#4

Вам в любом случае придется использовать делегат филда. Без этого вы просто не отловите ниодной операции с филдом.
И почему что-то должно сломаться? Что у вас за реализация такая?


#5

ну если вы наследуетесь от TF и внутри нового тф устанавливаете делегат, чтобы ограничить кол-во символов, а потом уже в VC опять определяете делегат, хотите сказать что делегат определеный внутри вашего tf будет работать?)

Ну вообще можно ведь боавлять обсерверы, у него ведь есть методы)


#6

повесьте делегат на любой другой NSObject


#7

собственно TF

class TF: UITextField {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension TF: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        print(string)
логика фильтрации
        return true
    }   
}

    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        
            let tf = TF(frame: CGRect(x: 100, y: 200, width: 200, height: 50))
            view.addSubview(tf)
            tf.backgroundColor = .red
фильтрация не работает
            **tf.delegate = self**
        }

тут в любом случае нужен либо прокси на делегат)) Либо что-то ниже уровнем, где эпл вызывает делегат))


#8

если вы определите делегат в VC, то уже в нем вы будете определять что выполнять. сперва определите для какого TF вы хотите что делать и на основе этого будете писать правило. если это не тот TF, который у вас со своим делегатом, то выполняете super методб иначе выполняете новое правило.


#9

в вашем случае ничего не нужно. проверьте попадаете ли вы в свой делегат внутри TF.
по сути у вас там принт стоит, если он срабатывает, то и логика фильтрации должна работать.

если же принт не срабатывает, это не проблемы VC.


#10

да что вы привязались к делегатам)))
Это все понятно и вполне адекватно вносить логику на ограничение символов в сам tf, а не таскать ее по vc
вот и спросил, может кто знает, что стоит ниже, откуда вообще идет смешивание вводимых символов с клавиатуры))
Вот и все)) А разговоров то пошло))
Может я конечно криво вопрос задал) тогда моя вина)


#11

короче, грубо говоря ваш код должен работать. если не работает, нужно понять что не так. может забыли что или перепутали. но я в коде не вижу ничего не правильного.

должно получиться примерно так

class TF: UITextField {

    var charLimit: Int = 1
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension TF: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        print(string)
        let currentCharacterCount = textField.text?.count ?? 0
    if range.length + range.location > currentCharacterCount {
        return false
    }
    let newLength = currentCharacterCount + string.count - range.length
    return newLength <= charLimit
    }   
}

    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        
            let tf = TF(frame: CGRect(x: 100, y: 200, width: 200, height: 50))
            view.addSubview(tf)
            tf.backgroundColor = .red
            tf.charLimit = 20
        }

#12

Мне кажется мы не понимаем друг друга))

Вы ведь понимаете, что у UITextField может быть всего один делегат, вот в делегате есть прекрасный метод
textField( _ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
с помощью которого можно фильтровать вводимые символы и собственно их количество

Если я внутри своего предопределенного TextField хочу реализовать логику фильтрации, но при этом мне с внешней стороны понадобится делегат, что мне делать?

Может так будет понятней вопрос?)))


#13

окей, не проблема

VC: UITextFieldDelegate {
   func viewDidLoad() {

   }

   func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if textField == tf { //нужно будет сделать ваш TF свойством для VC
            return super.textField(textField, shouldChangeCharactersIn : range, replacementString: string)
        } else {
            // тут уже ваше правило для других филдов
            return true
        }
    } 
}

P.S. только нужно будет проверить
либо super.textField, либо tf.textField
тут я не уверен как будет правильнее


#14

Это плохое решение, потому что UITextFieldDelegate не ограничивается одним методом)


#15

тогда копайте исходники Apple, что там за обсерверы они посылают. вдруг найдете


#16

Вооот)) В этом и был вопрос)) Может кто копал и что знает)))


#17

Решил с помощью swizzling на делегат UITextField и MulticastDelegate(не знаю, есть ли правильное название у этого подхода)


#18

Покажите всю реализацию, что бы понять, стоило оно того или нет.


#19
extension UITextField {

    private struct DelegateKeys {
        static var multicastDelegate = "multicastDelegate"
    }
    
    private var multicastDelegate: TextFieldMulticastDelegate? {
        get { objc_getAssociatedObject(self, &DelegateKeys.multicastDelegate) as? TextFieldMulticastDelegate }
        set { objc_setAssociatedObject(self, &DelegateKeys.multicastDelegate, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
    
    @objc private func _setDelegate(_ delegate: UITextFieldDelegate?) {
        if self is MaskedFieldProtocol {
            if let delegate = delegate {
                if let del = multicastDelegate {
                    del.multicastDelegate.add(delegate)
                    _setDelegate(del)
                } else {
                    let multicastDel = TextFieldMulticastDelegate(delegates: [delegate])
                    self.multicastDelegate = multicastDel
                    _setDelegate(multicastDel)
                }
            } else {
                
            }
            
        } else {
            _setDelegate(delegate)
        }
    }
    
    @objc private func _getDelegate() -> UITextFieldDelegate? {
        self is MaskedFieldProtocol ? multicastDelegate :  _getDelegate()
    }
    
    
    
    static let swizzle: Void = {
        
        let getterOriginalSelector = #selector(getter: UITextField.delegate)
        let setterOriginalSelector = #selector(setter: UITextField.delegate)
        
        let getterSwizzledSelector = #selector(_getDelegate)
        let setterSwizzledSelector = #selector(_setDelegate)

        guard
            let getterOriginalMethod = class_getInstanceMethod(UITextField.self, getterOriginalSelector),
            let setterOriginalMethod = class_getInstanceMethod(UITextField.self, setterOriginalSelector),
            let getterSwizzledMethod = class_getInstanceMethod(UITextField.self, getterSwizzledSelector),
            let setterSwizzledMethod = class_getInstanceMethod(UITextField.self, setterSwizzledSelector)
            else { return }
        
        method_exchangeImplementations(getterOriginalMethod, getterSwizzledMethod)
        method_exchangeImplementations(setterOriginalMethod, setterSwizzledMethod)
    }()
}



final class TextFieldMulticastDelegate: NSObject, UITextFieldDelegate {
    
    let multicastDelegate = MulticastDelegate<UITextFieldDelegate>()
    
    init(delegates: [UITextFieldDelegate]) {
        super.init()
        delegates.forEach { multicastDelegate.add($0) }
    }

**определяем методы**

}

protocol MaskedFieldProtocol: class { }

extension MaskedFieldProtocol where Self: UITextField { } устанавливаю delegate на self, и использую необходимые методы делегата

Использую просто:

class TF: UITextField, MaskedFieldProtocol {

bla bla bla

}

#20

Если честно, я так и не понял сути всего этохо обхода.
Ведь все тоже самое можно делать и с обычным кастомным элементом. Использовать методы делегата, только которые необходимы, задавать в них общую логику поведения. А если в какомто месте нужно логику изменить, то меняется в конкретном случае метод делегата.