トグルの値が変更された時に Toggle.OnValueChangedAsObservable を1度目は発火させず2度目以降は発火させる【UniRx / SetIsOnWithoutNotify】
Toggle.OnValueChange,初期値は発火させたくないけど,二度目以降は発火させるようにする奴です.
問題提起
MVPパターンで例えばModel側でboolの状態を管理し,View側でbool値を変更し,かつbool値の状態をUIに反映させる例を挙げます.
Model
using UniRx; using UnityEngine; public class SampleModel : MonoBehaviour { private BoolReactiveProperty _isOn = new BoolReactiveProperty(false); public IReadOnlyReactiveProperty<bool> IsOn => _isOn; /// <summary> /// bool値の状態を変更する /// </summary> public void SwitchValue(bool isOn) { Debug.Log("Switched Value."); Debug.Log(isOn ? "ON." : "OFF."); _isOn.Value = isOn; } }
Presenter
using UnityEngine; using UniRx; public class SamplePresenter : MonoBehaviour { [SerializeField] private SampleView _view; [SerializeField] private SampleModel _model; private void Awake() { // Viewのトグルの値が変更されたら,Modelへ通知して,bool値の状態を変更する _view.OnToggleClicked.Subscribe(_model.SwitchValue).AddTo(this); // Modelのbool値の状態が変更されたら,Viewへ通知して,ToggleUIのオン・オフを表示を反映させる _model.IsOn.Subscribe(_view.ActivateToggle).AddTo(this); } }
View
using System; using UniRx; using UnityEngine; using UnityEngine.UI; public class SampleView : MonoBehaviour { [SerializeField] private Toggle _toggle; private Subject<bool> _OnToggleClicked = new Subject<bool>(); public IObservable<bool> OnToggleClicked => _OnToggleClicked; private void Awake() => _toggle.OnValueChangedAsObservable().Subscribe(_OnToggleClicked.OnNext).AddTo(this); /// <summary> /// トグルの表示状態を変更する /// </summary> public void ActivateToggle(bool isOn) => _toggle.isOn = isOn; }
Toggle の isOn 初期値は true として,この状態でシーンを起動してみます.
ReactiveProperty の特性上,初期値falseがOnNextで飛び,View側に通知されます. ViewのActivateToggleで Toggle の isOn が true から false に変わるため, ここで OnValueChangedAsObservable が発火し,Model側へ通知されます. そして,SwitchValue の中身が呼び出されます.
ここで問題なのが,シーン実行から初期値が設定される際に OnValueChangedAsObservable が発火されるために, 不必要に 1度 SwitchValue が呼び出されるところです.コンソールに一度のDebug.Log が出てしまいますが, これをやりたくないというのがモチベーションです.
解決方法 |SetIsOnWithoutNotify
Unity 2019.1 から追加された SetIsOnWithoutNotify というメソッドを利用します. これは OnValueChanged を発火させずに,Toggle の isOn を設定することができるメソッドです.
これを利用した解決方法はこのようになります.
- 1度目は Toggle.SetIsOnWithoutNotify(isOn) で Toggle の値を設定する
- 2度目以降は Toggle.isOn = isOn で Toggle の値を設定する
以上を満足できるように View と Presenter 側の処理を書き換えます.
View
using System; using UniRx; using UnityEngine; using UnityEngine.UI; public class SampleView : MonoBehaviour { [SerializeField] private Toggle _toggle; private Subject<bool> _OnToggleClicked = new Subject<bool>(); public IObservable<bool> OnToggleClicked => _OnToggleClicked; private void Awake() => _toggle.OnValueChangedAsObservable().Subscribe(_OnToggleClicked.OnNext).AddTo(this); /// <summary> /// トグルの表示状態を変更する /// </summary> public void ActivateToggle(bool isOn) => _toggle.isOn = isOn; /// <summary> /// OnValueChanged を発火させずにトグルの表示状態を変更する /// </summary> public void SetToggleValueWithoutNotify(bool isOn) => _toggle.SetIsOnWithoutNotify(isOn); }
Presenter
using UnityEngine; using UniRx; public class SamplePresenter : MonoBehaviour { [SerializeField] private SampleView _view; [SerializeField] private SampleModel _model; private void Awake() { // Viewのトグルの値が変更されたら,Modelへ通知して,bool値の状態を変更する _view.OnToggleClicked.Subscribe(_model.SwitchValue).AddTo(this); // Modelのbool値の状態が変更されたら,Viewへ通知して,ToggleUIのオン・オフを表示を反映させる // 1回目は OnValueChanged を発火させない _model.IsOn.First().Subscribe(_view.SetToggleValueWithoutNotify).AddTo(this); // 2回目以降は OnValueChanged を発火させる _model.IsOn.Skip(1).Subscribe(_view.ActivateToggle).AddTo(this); } }
これで動作確認してみます.
シーン実行時にログを吐かなくなり,UIでトグルの値を変更する時にだけ吐くようになりました.
参考資料
Toggle以外のUIでも似た機能があります.SetValueWithoutNotify.
他に良い方法があればご教授頂けると幸いです.