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
How easy is to reproduce? (chances of successful reproduce after running the self contained code)
Xcode version:
Version 8.3.3 (8E3004b)
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:
textFieldShouldReturn. You should only use this method to return true/false depending on validation logic.streetNumberTf.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { _ in
self.streetNumberTf.nextTextField?.becomeFirstResponder()
})
.disposed(by: disposeBag)
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.
Most helpful comment
Hi @amaurydavid ,
There are a couple of things:
textFieldShouldReturn. You should only use this method to returntrue/falsedepending on validation logic.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.