New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

`UITextView.rx_text` observe programmatic text changes. #551

Closed
tarunon opened this Issue Mar 15, 2016 · 22 comments

Comments

Projects
None yet
9 participants
@tarunon
Copy link
Contributor

tarunon commented Mar 15, 2016

I found a my unintended behavior in this case.

let subscription = textField.rx_text.subscribeNext { print($0) }
textField.text = "It is never observed in subscription馃様"

By the way, UITextView.rx_text can observe changes of text programmatically.

let subscription = textView.rx_text.subscribeNext { print($0) }
textView.text = "It is observed in subscription馃槃"

It is caused by the difference of implement these.
UITextField.rx_text use UIControl.rx_value, and UIControl.rx_value seems observe changes caused only UIEvent.

static func rx_value<C: AnyObject, T: Equatable>(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty<T> {

I read the discussion #471, I think we need to discussion continue.
There is the same type but these contain different behavior.
I think it would lead to misunderstanding.(least, UITextField and UITextView)

@tarunon

This comment has been minimized.

Copy link
Contributor Author

tarunon commented Mar 15, 2016

I implemented UIControl.rx_value with KVO event. (#552)
I think it may work well.

@sergdort

This comment has been minimized.

Copy link
Collaborator

sergdort commented Mar 15, 2016

Hi @tarunon this does not work because it's not a control event.
And as far as I remember its not possible to observe text property via KVO

The only "hack" that you can make is to tell manually to UIControl that value has changed
via sendActionsForControlEvents then it will notify your observers

@tarunon

This comment has been minimized.

Copy link
Contributor Author

tarunon commented Mar 15, 2016

Hi @sergdort san,
Thanks for your point 馃槃
Yes, KVO does not observe the value change caused UIEvent.
So, I implemented combine KVO and ControlEvent now.
Do you know other problem and pointed that?

@sergdort

This comment has been minimized.

Copy link
Collaborator

sergdort commented Mar 15, 2016

@tarunon
Well, I thing that values in the UITextField and UITextView are always reflection of some values of Model or ViewModel layer
So I don't understand why programmatic changes of text property should "send" events
But rather changes of view model should change the value of the text via some bindings and vice versa

@tarunon

This comment has been minimized.

Copy link
Contributor Author

tarunon commented Mar 15, 2016

@sergdort
Hmm, It is true, I think it's beautiful design and we should make so.
But this problem is about API.
I think it would lead misunderstanding that same name parameters of the same type have a different behavior.

@sergdort

This comment has been minimized.

Copy link
Collaborator

sergdort commented Mar 15, 2016

@tarunon
From my perspective:
Let's imagine you work in traditional way without Rx, to achieve rx_text behavior you would need to implement delegate or add target for control events. And in this case you also will not be notified if you would change the value of the text property programmatically

Since rx_text is ControlProperty I don't see problem in API, because "event sending" based on the user interaction

@kzaher

This comment has been minimized.

Copy link
Member

kzaher commented Mar 15, 2016

Hi guys,

If UITextField and UITextView behave differently then we would need to unify them. We would want to be consistent with platform and just report user initiated changes.

If I understood what @tarunon is saying, I think part of the problem with UITextView might be that we've used a different API to observe changes, and that API might not be only reporting user initiated changes.

The reason why we've used that API there is because we've tried to solve problems with autocorrect and the current solution seemed to remedy that situation.

I think we'll maybe need to return that code to more hacky form we had before :(. Looks like I will need to investigate that one again :(

@tarunon

This comment has been minimized.

Copy link
Contributor Author

tarunon commented Mar 16, 2016

@kzaher
Thank you.
If my understanding is correct, this discussion is about UITextView.rx_text observe programmatically change.
And my #552 pr will not be required, I'll close it. 馃槃
Should I change this issues title?

@hsoi

This comment has been minimized.

Copy link
Contributor

hsoi commented Mar 16, 2016

FWIW, I think a problem I'm having is due to this.

I posted about it to the RxSwift Slack.

Hello. I'm new to RxSwift, diving in for the first time.

Trying to figure out how to keep 2 UITextFields in sync, with validation intact.

I have a "Login/Join" ViewController, that has 2 views: 1 for new users to sign up, 1 for existing users to log in. Two fields in common between the views are the "username" and "password" fields (unique UITextField instances). Designs are to keep them in sync, so if a user starts typing a username on the Login view then switches to the Join view, the Join view's username field is pre-filled with the Login view's username field value.

Following the "SimpleValidationViewController" example, I've gotten things working. And it seems I can keep the fields in sync doing something like:

joinEmailField.rx_text.bindTo(loginEmailField.rx_text).addDisposableTo(disposeBag)

And while that keeps the fields in sync, the validation on the field (just like in the sample code, a map to ensure at least 1 character entered) does not kick in. So if I enter a valid username on the Join view and switch to Login, while the username field is filled in, none of the validation bindings have kicked in (and my "Login/Join" button doesn't enable). If I tap into the username field, validations kick in -- but that's not desireable.

What am I missing to make the validations work?

Thank you.

I'm mentioning it here in case 1. it is directly related, 2. to provide a use case.

When I originally started to implement my setup (again, I'm a RxSwift n00b so I was doing it piece by piece), when in code I'd switch from one view to the other I simply did something like:

textField1.text = textField2.text

And expected it would work. Yes text was transfered over, but none of the extra validations, etc.

So I tried the above binding and that too works (and seems like the more Rx-way to do things), but again it just transfers the text, not the validations.

Edited: I tried @sergdort 's sendActionsForControlEvents suggestion, and it works (I put it where my user switches from one view to the other). From what @fpillet mentioned to me on Slack, it sounded like "this is how it is". If so, that's fair, but it does seem unexpected. And I'll agree with @kzaher 's desire for consistency. It may not be possible, but then it'd be at least welcome to document it as such.

@kzaher

This comment has been minimized.

Copy link
Member

kzaher commented Mar 16, 2016

Hi @tarunon ,

I think we should probably change title to something related with UITextView and stopping propagating programatic updates for rx_text, if I understood the issue correctly :)

@kzaher

This comment has been minimized.

Copy link
Member

kzaher commented Mar 16, 2016

Hi @hsoi ,

it is my understanding this code sample

joinEmailField.rx_text.bindTo(loginEmailField.rx_text).addDisposableTo(disposeBag)

works as advertised :)

Thnx for trying to help, but I think this is another issue :)

@hsoi

This comment has been minimized.

Copy link
Contributor

hsoi commented Mar 16, 2016

@kzaher well yes that works in terms of keeping the texts in sync. BUT it does not trigger the validations and other things. That's the problem and does seem to be the same issue.

I worked around it by using the sendActionsForControlEvents(.ValueChanged) workaround, which... works around it. But I agree with your statement that having consistency and unity is the way to go.

@tarunon tarunon changed the title `UITextField.rx_text` never observe when change text programmatically. `UITextView.rx_text` observe programmatic text changes. Mar 17, 2016

@kzaher

This comment has been minimized.

Copy link
Member

kzaher commented Mar 18, 2016

Hi guys,

I've checked current UITextView.rx_text behavior.

For this code

textView.rx_text.asObservable()
            .subscribeNext { [weak self] x in
                self?.debug("UITextView text \(x)")
                self!.textView.text = "c"
            }
            .addDisposableTo(disposeBag)

        textView.text = "1223423"
        textView.text = "a"
        textView.text = "b"
        textView.text = "c"

... self?.debug("UITextView text \(x)") will only be called once and it will print

"UITextView text c"
  • If you set equal value, it won't be reemited.
  • If you set multiple values in one run loop block, it will only only emit value in the following run loop if the text value has changed.

If we can somehow improve this behavior and make it even more consistent, that would be great, but I don't think it's significantly different then other rx_text behaviors, although I can see how emitting that programmatically changed value can be little confusing.

The reason why it's like this is because UITextView has a quirk with autocorrect, and it behaves different then UITextField, so we couldn't just rely on UIControlEventValueChanged.

@hsoi I think there is a better way then sendActionsForControlEvents(.ValueChanged) if this creates a problem for you :)
If you really want it to behave like a variable you can just bind it to a Variable.

        let textValue = Variable("")
        textField.rx_text <-> textValue

... and then just work with textValue variable which will behave like you expect.

@kzaher

This comment has been minimized.

Copy link
Member

kzaher commented Apr 26, 2016

This looks stale to me. I think we can close this one. We can reopen it if needed.

@Natai

This comment has been minimized.

Copy link

Natai commented Nov 18, 2016

@kzaher I did as you say

    let textValue = Variable("")
    textField.rx.textInput <-> textValue

but have some trouble after textField.resignFirstResponder(). the textField.rx.text whould not respond to the change of textValue.

@AlexisQapa

This comment has been minimized.

Copy link

AlexisQapa commented Nov 29, 2018

Does anyone came with a solution to observe only user input without any programatic text change like on textfields ?

@freak4pc

This comment has been minimized.

Copy link
Collaborator

freak4pc commented Nov 29, 2018

sendActions on the text field.

@AlexisQapa

This comment has been minimized.

Copy link

AlexisQapa commented Nov 29, 2018

What a quick response :) looking into it

@AlexisQapa

This comment has been minimized.

Copy link

AlexisQapa commented Nov 29, 2018

Sorry my question wasn't clear. I need to observe user only inputs on a UITextView. The behaviour works on a UITextField. So I can't use sendActions.

@giosad

This comment has been minimized.

Copy link

giosad commented Dec 26, 2018

@AlexisQapa, try this

    _ = textView.rx.didChange.map { [weak self] in
      return self?.textView.text
    }.subscribe(onNext: { text in
      print("didChange text ", text)
    })
@nilehmann

This comment has been minimized.

Copy link

nilehmann commented Feb 5, 2019

Hi, I'm having the same issue here. So far, I think @giosad suggestion is working for me, but then, besides the first one emitting programmatic changes, what's the difference between:

textView.rx.text

and

textView.rx.didChange.map { textView.text }

?
I'm not being able to replicate any issues with autocorrect in the second case. Maybe just a really weird edge case?

@nilehmann

This comment has been minimized.

Copy link

nilehmann commented Feb 5, 2019

Ok, don't worry, now I get it. The problem happens when you tap outside the text view while there's a "pending" auto-correction. In that case, didChange doesn't emit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment