Rxswift: Using UITextField.rx.text prevents the textField to become first responder

Created on 7 Aug 2017  路  4Comments  路  Source: ReactiveX/RxSwift

Short description of the issue:

Using rx.text on a UITextField prevents it to become first responder, or more precisely, seems to resign it almost immediately

Expected outcome:

Be able to observe text changes along with being able to become first responder without any interference

What actually happens:

In the below example, I want the streetTf to become the first responder when the user taps the streetNumberTf return key.
Without subscribing to streetTf.rx.text, all works fine and the streetTf is correctly focused when I tap the return key.
Although, when I subscribe to streetTf.rx.text, the streetTf's textField:didEndEditing:is called as soon the textField:didBegingEditing returns, hence dismissing the keyboard.

I also noticed that subscribing to streetNumberTf doesn't impact the described behavior.

Self contained code example that reproduces the issue:

extension UITextField {

    private static var _handle: UInt8 = 0

    @IBOutlet var nextTextField: UITextField? {
        set {
            objc_setAssociatedObject(self, &UITextField._handle, newValue, .OBJC_ASSOCIATION_ASSIGN)
        }
        get {
            return objc_getAssociatedObject(self, &UITextField._handle) as? UITextField
        }
    }
}

class AddressView : UIView {
    let streetNumberTf = UITextField()
    let streetTf = UITextField()
    let disposeBag = DisposeBag()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        streetNumberTf.placeholder = "street number"
        streetNumberTf.delegate = self
        streetTf.placeholder = "street"
        streetTf.delegate = self
        streetNumberTf.nextTextField = streetTf
        //Comment the following line and it behaves as expected
        streetTf.rx.text.subscribe().addDisposableTo(disposeBag)

        streetNumberTf.translatesAutoresizingMaskIntoConstraints = false
        streetTf.translatesAutoresizingMaskIntoConstraints = false
        addSubview(streetNumberTf)
        addSubview(streetTf)

        streetNumberTf.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
        streetNumberTf.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
        streetNumberTf.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
        streetNumberTf.bottomAnchor.constraint(equalTo: streetTf.topAnchor).isActive = true
        streetTf.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
        streetTf.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
        streetTf.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true
    }
}

extension AddressView : UITextFieldDelegate {
    func textFieldDidBeginEditing(_ textField: UITextField) {
        print("\(#function) L\(#line) \(textField)")
    }

    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
        print("\(#function) L\(#line) \(textField)")
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        print("\(#function) L\(#line) \(textField)")
        textField.resignFirstResponder()
        textField.nextTextField?.becomeFirstResponder()
        return true
    }
}

RxSwift/RxCocoa/RxBlocking/RxTest version/commit

RxSwift 3.3.1 & RxCocoa 3.3.1

Platform/Environment

  • [x ] iOS
  • [ ] macOS
  • [ ] tvOS
  • [ ] watchOS
  • [ ] playgrounds

How easy is to reproduce? (chances of successful reproduce after running the self contained code)

  • [x ] easy, 100% repro
  • [ ] sometimes, 10%-100%
  • [ ] hard, 2% - 10%
  • [ ] extremely hard, %0 - 2%

Xcode version:

Version 8.3.3 (8E3004b)

Most helpful comment

Hi @amaurydavid ,

There are a couple of things:

  • you shouldn't perform sideeffects inside textFieldShouldReturn. You should only use this method to return true/false depending on validation logic.
  • the API you want to use probably looks something like this
streetNumberTf.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { _ in
            self.streetNumberTf.nextTextField?.becomeFirstResponder()
        })
        .disposed(by: disposeBag)
  • the behavior you are experiencing is a result of registering event handler on textfield (streetTf.addTarget(self, action: #selector(AddressView.a), for: [.allEvents])) + your invalid usage of delegate method.

As far as I can tell this is not an issue with this library.

All 4 comments

I'll try looking at your example a bit later for you but just wanted to share that many pieces of my code toggle becomeFirstResponder on a UITextField along with using rx.text without any issues, so this is probably a problem specific to your implementation and not something specific to RxSwfit/RxCocoa.

By the way is there any reason you manually resign one first responder in favor of the other? Becoming first responder already resigns the other first responder.

Nope, I can replace it by

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let next = textField.nextTextField {
            next.becomeFirstResponder()
        } else {
            textField.resignFirstResponder()
        }
        return true
    }

though it doesn't solve the issue.

Hi @amaurydavid ,

There are a couple of things:

  • you shouldn't perform sideeffects inside textFieldShouldReturn. You should only use this method to return true/false depending on validation logic.
  • the API you want to use probably looks something like this
streetNumberTf.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { _ in
            self.streetNumberTf.nextTextField?.becomeFirstResponder()
        })
        .disposed(by: disposeBag)
  • the behavior you are experiencing is a result of registering event handler on textfield (streetTf.addTarget(self, action: #selector(AddressView.a), for: [.allEvents])) + your invalid usage of delegate method.

As far as I can tell this is not an issue with this library.

Nice thanks for the help!
I wasn't aware of the existence of an editingDidEndOnExit controlEvent, your solution works like a charm.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

acecilia picture acecilia  路  3Comments

jeremiegirault picture jeremiegirault  路  3Comments

delebedev picture delebedev  路  3Comments

trant picture trant  路  3Comments

gaudecker picture gaudecker  路  3Comments