久々の更新です。
先週の土曜日から昨日まで、とにかくPCから離れてのびのびと休暇を取っていました。
今日からまた、新生活の開始と共にぼちぼちこちらも更新していけたらと思っています。
今回は、RxSwiftの練習として、 UIScrollView でいまスクロールしている方向(右か左か、上か下か)を流すObservable
を定義してみます。
zip、skipを使う
UIScrollView でスクロールをしている方向を取る方法として、現在の contentOffset と、1つ前の contentOffset を取得してその差を見て判断する方法があります。
以下のように定義をすると、 (oldOffset, newOffset) のTupleの組で取得することができます。
import RxSwift
import RxCocoa
extension UIScrollView {
var rx_contentOffsetDiff: Observable<(CGPoint, CGPoint)> {
return Observable.zip(rx_contentOffset, rx_contentOffset.skip(1)) { ($0, $1) }
}
}
rx_contentOffset は、 RxCocoa で定義されている、 contentOffset の変化をキャッチできる Observable です。
- zip は2つのObservableを1つにする時に使います。
- skip は、指定した回数分、流れてくるストリームをスキップして、その後に流れてきたものをキャッチするようにします
なので、この場合は、 rx_contentOffset (old) と、 rx_contentOffset.skip(1) と、1回スキップしたもの、つまり(old)より1つ新しいもの (new) を zip 関数でTupleの組にして返します。
定義した rx_contentOffsetDiff を使って方向をObservableを使って返す
enum ScrollDirection {
case None
case Up
case Down
case Left
case Right
}
こんな感じで enum を定義します。あとはこれと、 rx_contentOffsetDiff を使って、
UIScrollView でいまスクロールしている方向(右か左か、上か下か)を流すObservable
を定義します。
4方向を1つの関数でやるのはあまり良くないので、 垂直方向 、 水平方向 に分けて定義します。
extension UIScrollView {
var rx_verticalScrollDirection: Observable<ScrollDirection> {
return rx_contentOffsetDiff.flatMap { (old, new) in
Observable<ScrollDirection>.create { observe in
let direction = old.y < new.y ? ScrollDirection.Up : old.y > new.y ? .Down : .None
observe.onNext(direction)
observe.onCompleted()
return AnonymousDisposable {}
}
}
}
var rx_horizontalScrollDirection: Observable<ScrollDirection> {
return rx_contentOffsetDiff.flatMap { (old, new) in
Observable<ScrollDirection>.create { observe in
let direction = old.x < new.x ? ScrollDirection.Left : old.x > new.x ? .Right : .None
observe.onNext(direction)
observe.onCompleted()
return AnonymousDisposable {}
}
}
}
}
また、例えば垂直方向を検知する時に、y座標に変化がない場合には、どちらにもスクロールしていないということで、 .None を返します。
contentOffset に限らず、うまく skip と zip を組み合わせてあげると、値に関して新旧のものを同時に受け取ることができるので、便利です。
ソース全文
import Foundation
import UIKit
import RxSwift
import RxCocoa
enum ScrollDirection {
case None
case Up
case Down
case Left
case Right
}
extension UIScrollView {
private var rx_contentOffsetDiff: Observable<(CGPoint, CGPoint)> {
return Observable.zip(rx_contentOffset, rx_contentOffset.skip(1)) { ($0, $1) }
}
var rx_verticalScrollDirection: Observable<ScrollDirection> {
return rx_contentOffsetDiff.flatMap { (old, new) in
Observable<ScrollDirection>.create { observe in
let direction = old.y < new.y ? ScrollDirection.Up : old.y > new.y ? .Down : .None
observe.onNext(direction)
observe.onCompleted()
return AnonymousDisposable {}
}
}
}
var rx_horizontalScrollDirection: Observable<ScrollDirection> {
return rx_contentOffsetDiff.flatMap { (old, new) in
Observable<ScrollDirection>.create { observe in
let direction = old.x < new.x ? ScrollDirection.Left : old.x > new.x ? .Right : .None
observe.onNext(direction)
observe.onCompleted()
return AnonymousDisposable {}
}
}
}
}