UITextField EditingDidBegin triggered twice on IOS 14.4

Viewed 412

I have two textfields. I want to open a bottomsheet when the first textfield is tapped. And the other textfield is accepting user input regularly.

This is my trigger when the produklistrikDropdown text field with edittingDidBegin.

produkListrikDropDown.rx.controlEvent(.editingDidBegin)
            .subscribe(onNext:{
                self.dismissKeyboard()
                print("produk Listrik Field did begin")
                self.viewModel.showProdukListrik()
            })
            .disposed(by: disposeBag)

And after I choose a product from the bottom sheet it just change the text of the field to the one i choose.

viewModel.currentProdukListBehaviorSubject
            .map{$0?.rawValue}
            .subscribe(onNext: { [unowned self] in
                if produkListrikDropDown.text != $0 {
                    produkListrikDropDown.text = $0
                }
            })
            .disposed(by: disposeBag)

This is my second textfield configuration. Just a regular textfield receiving input from the user.

idPelangganField.rx.text
            .bind(to: viewModel.currentIdPelangganBehaviorSubject)
            .disposed(by: disposeBag)

What Happen

When I tapped the first textfield and choose a product, its acting okay. But when I tapped the second textfield and tapped back to the first textfield, the edittingDidBegin on the first textfield triggered twice. This doesn't happen on IOS 12.4.

You can clone this repo to reproduce the problem https://github.com/Bobbyphtr/TextfieldDidEdittingProblem/tree/master

2 Answers

The problem is that you are performing side effects in textFieldShouldBeginEditing(_:). The operating system is free to call this function as often as it wants because the only thing the function is for is to find out whether the SDK should allow the text field to begin editing.

Read up on Command-Query Separation (Not just the Wikipedia article. Do an internet search and read other articles about it as well.)

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer.1 More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

The solution to your problem is to use a UILabel with a tap gesture recognizer on it instead of a text field. Make sure you set the label's isInteractionEnabled to true.


I found a second problem in your code as well. The Observable returned by showBottomSheetPicker(selectionList:) never completes. Make sure you call observer.onCompleted() after sending the next event.

I've found a better way without sacrificing the use of textfield. Like @Daniel T. said, that we can use a tap gesture recognizer on it. I put a Tag Gesture Recognizer for the UITextField and set it to receive touch down events. It eliminates any side effects of the textFieldShouldBeginEditting(_:) and also not use any RxSwift to make the call.

Related