SwiftChainingの解説記事その2です。
前回の記事はこちら -> Swiftでデータバインディングするライブラリを作った
iOSアプリでデータバインディングをするからには、UIと値を同期できないと始まりません。
今回は例として、UISwitch
とバインディングする例を紹介します。
UISwitchとバインディングする
UISwitch
のスイッチのON・OFFの状態であるisOn
と、前回の記事で紹介したValueHolder
の値をバインディングしてみます。コードは以下の通りです。
import UIKit
import Chaining
class ExampleViewController: UIViewController {
@IBOutlet weak var mySwitch: UISwitch!
lazy var isOnAdapter = { KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )}()
lazy var valueChangedAdapter = { UIControlAdapter(self.mySwitch, events: .valueChanged) }()
let isOnHolder = ValueHolder(true)
let pool = ObserverPool()
override func viewDidLoad() {
super.viewDidLoad()
self.isOnHolder.chain().sendTo(self.isOnAdapter).sync().addTo(self.pool)
self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end().addTo(self.pool)
}
}
解説
KVOに対応したプロパティをSwiftChaining
で扱えるようにするためのクラスがKVOAdapter
です。
KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )
対象となるオブジェクトとプロパティのkeyPath
を渡して生成し、値を監視したり更新したりできます。
ValueHolder
同士の場合と同じく、以下のようにsendTo
にKVOAdapter
を渡すことでValueHolder
からの値を受け取ることができます。
self.isOnHolder.chain().sendTo(self.isOnAdapter).sync()
逆にKVOAdapter
からisOn
の変更を受け取るのには、以下のように逆方向に繋げれば良いのですが、今回の場合、残念ながらうまくいきません。
// うまくいかない
self.isOnAdapter.chain().sendTo(self.isOnHolder).sync()
UISwitch
をタップしてON・OFFの状態を変更したときはKVOでは通知されないので、UIControl
のイベントを監視するようにします。
UIControl
のイベントを監視するクラスとしてUIControlAdapter
を用意しています。
UIControlAdapter(self.mySwitch, events: .valueChanged)
events
で監視したいUIControl
のイベントを指定します。
UIControlAdapter
からイベントを監視してValueHolder
に値を反映させるには以下のように書きます。
self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end()
UIControlAdapter
から送られる値はUIControl
(ここではUISwitch
)なので途中でmap
関数を使ってisOn
の値に変換してValueHolder
が受け取れるようにしています。
このように、それぞれ変更があった時に双方向に値を送り合うことで、値を同期することができるという感じになります
SwiftChaining
では値の送信が循環しても延々とループにならないように内部でロックしているので、それこそchain
したオブジェクトをsendTo
にそのまま渡すようなことをしてもハングアップしたりはしません。
ObserverPool
ちなみに、今回のコードで出てきたクラスにObserverPool
というものがあります。
let pool = ObserverPool()
...
self.isOnHolder.chain().sendTo(self.isOnAdapter).sync().addTo(self.pool)
複数のObserverを保持しておかないといけない時に別々に管理するのは面倒なので、Observerをまとめて保持しておけるものとして用意しています。ObserverPool
を破棄したりinvalidate()
を呼んだら、保持しているObserverがまとめて無効になります。
Observerの追加をObserverPool
側の関数でやると書きにくかったので、sync
やend
の後にそのままaddTo()
をつなげて書いて追加できるようにしています。
補足
なお今回紹介したコードだと、ValueHolder
からUISwitch
へ値を反映させる時にアニメーションをしなかったり、UISwitch
のisOn
をコードで直接変更した時にValueHolder
へ値が反映されません。それらが必要であれば、さらにコードを追加・変更することになるでしょう。
SwiftChaining
はUIKit
をラップして簡単にするものではないので、このようなUIKit
の対応は通常と変わらず考える必要があります。