Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift replacement shouldChangeCharactersInRange

I want use UITextfield with RxSwift. My goal is allowing/not input character in User keyboard and removing character from copy paste, I need handle UITextfield's delegate "shouldChangeCharactersInRange" with RxSwift.

How to implement with RxSwift?

I am using RxSwift version 4. Case 1: Input from keyboard: A123 Process from RxSwift : Accept 123 (not allowing NumberPad) Output : 123

Case 2: Input form Copy Paste from Contacts: \U202d1111111111\U202c Process from RxSwift : remove all control character, accept 1111111111 Output: 1111111111

If in general we can use shouldChangeCharactersInRange , but how to use with RxSwift?

like image 378
David Sujarwadi Avatar asked May 28 '18 03:05

David Sujarwadi


1 Answers

In general, you should not be mutating state in shouldChangeCharactersInRange, even if you aren't using Rx. That callback is a query not a command. The textfield is merely asking you if it should perform the default behavior, not telling you to update it. The behavior you are trying to implement should be in the editingChanged action.

Since you are using Rx, the text field's rx.text observer is equivalent to the editingChanged action and should be used instead. The hardest part of the procedure is making sure you don't loose the user's place if they are inserting/deleting in the middle of the string.

In your viewDidLoad:

textField.rx.text.orEmpty
    .map(digitsOnly)
    .subscribe(onNext: setPreservingCursor(on: textField))
    .disposed(by: bag)

Supporting global functions:

func digitsOnly(_ text: String) -> String {
    return text.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
}

func setPreservingCursor(on textField: UITextField) -> (_ newText: String) -> Void {
    return { newText in
        let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: textField.selectedTextRange!.start) + newText.count - (textField.text?.count ?? 0)
        textField.text = newText
        if let newPosition = textField.position(from: textField.beginningOfDocument, offset: cursorPosition) {
            textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
        }
    }
}

BTW, even if you are presenting the number pad keyboard, you still need some code like this because the user might have a bluetooth keyboard hooked up and thus could still enter non-numbers.

like image 73
Daniel T. Avatar answered Nov 07 '22 12:11

Daniel T.