ポジティブ丸メガネ

3年目エンジニアです。

RxSwiftでUITextFieldにコードから値を代入してもイベントが流れない

環境

業務でiOSアプリ開発ではRxSwiftとMVVMを導入しています。開発をはじめて1ヶ月半というところです。

  • Xcode 8.3.1
  • Swift 3
  • RxSwift 3.5.0

TL;DR

  • textField.text = "hoge"してもtextFieldのストリームにイベントが流れてこない
  • コードから代入したら、textField.sendActions(for: .valueChange)してやれば流れる

何をしようとして何が起こった

後輩くんが、日付とメッセージを入力する画面をつくっていました(本当はもっとたくさん入力する)。日付毎に入力するタイプのものなので、ユーザーが選択した日付に対応するメッセージがRealmに存在していた場合は、メッセージ入力フォームに予め値を入れておいてあげる必要がありました。また、日付とメッセージが入力されていた場合、保存ボタンを有効にするようにしていました。

組んでいたストリームの流れは、

  1. VC:dateField → VM:date
  2. VM:dateの値でRealmのデータを検索して、
    • 存在すればVM:messageに代入
    • しなければなにもしない
  3. VM:messageTemp → VC:messageField
  4. VM:date & message が入力されていればVM:isSaveButtonEnabledをtrueに
  5. VM:isSaveButtonEnabled → VC:saveButton.isEnabled

というシンプルなものでした。レビューした感じだと、日付に対応するデータが存在しない場合はうまく動いていましたし、存在する場合でもmessageFieldにRealmから取ってきたデータがバインドされていました。しかし、存在する場合にsaveButtonが有効になっていませんでした。

messageFieldに値は入力されているのに、ボタンは有効になっていない。messageFieldをタップすれば有効になる。そんな状態でした。

原因と解決策

ひとまずあらゆるストリームに.debug()をつけて原因を調べてみました。すると、上記の2.までは流れているが3.にイベントが流れてきていませんでした。

何が原因なのかググってみると、下記issueを発見。1年以上前のissue。。。

github.com

さてさて、読んでみるとこんなことが書いてありました。

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

なるほど、コードから代入するのはControl Eventじゃないからうまく動いていないようです。よってそのEventを自分でハックすればできるよって感じですね。

なので早速3.をbind(to:)ではなくsubscribe()で下記のように書くようにしました。

vm.message.subscribe(onNext: { [weak self] message in 
    self?.messageField.text = message
    self?.messageField.sendActions(for: .valueChanged)
})....

すると想定したように保存ボタンが有効になるようになりました。

この状況を簡単に再現したものを作ってみたのでよかったら見てみてください。TwoWayBinding*.swiftです。

github.com

しかし、先程のissueを読み進めていくとこんなことが書いてありました。

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.

いやー本当にそのとおりですね。ちゃんと設計すればこの問題は起こらなかったですし、それに気づけなかった僕はまだまだダメです。

おわり