こんにちは、iOSチームの本柳です。
UIScrollViewのスクロールしている方向を取扱いたいことって割りとありますよね?
通常はscrollViewWillBeginDragging
でスクロール開始位置のcontentOffset
を保存しておいて、scrollViewDidScroll
で現在のcontentOffset
と保存しておいたcontentOffset
を比較して方向を取るみたいなことをするかと思います*1。
しかし、この方法、Viewにスクロール開始時の状態を増やしてしまったり、スクロール方向を決める処理の実装が煩雑だったりと個人的にあまり好きではありません。
今回は、別のアプローチでスクロール方向を取得する方法を考えてみました。
KVOを利用してcontentOffsetを監視する
ScrollViewがスクロールするとcontentOffsetが更新されます。
なので、contentOffsetを監視すればscrollDidScroll
と同じようにスクロールした時というのを取得することが可能となります。
監視するための処理をextensionを利用してUIScrollViewに拡張しましょう。
extension ScrollDetectable where Self: UIScrollView { var scrollDetectableKeyPath: String { return "contentOffset" } /// contentOffsetの監視を始める func enableScrollDirectionDetect() { var _self = self _self.scrollObserved = true addObserver(self, forKeyPath: scrollDetectableKeyPath, options: [.new, .old], context: nil) } /// contentOffsetの監視をやめる func disableScrollDirectionDetect() { if scrollObserved { var _self = self _self.scrollObserved = false removeObserver(self, forKeyPath: scrollDetectableKeyPath) } } }
KVOを利用して監視を行うと、プロパティの変更前、変更後の値を取得出来るのでスクロールの開始位置を保存しておく必要がなくなります。
スクロール方向は型で欲しい
スクロール方向は型として取り扱いたいですよね?
ということでスクロール方向を型にしてみます。
// 横方向のスクロール状態 enum ScrollDirectionX { case none, lead, tail } // 縦方向のスクロール状態 enum ScrollDirectionY { case none, top, bottom }
そういうケースがあるか分からないのですが、斜め方向へのスクロールのことも考慮して縦と横のスクロールで型を分けるようにしました。
スクロール方向をstructとして定義する
スクロール方向に関する情報は一つのオブジェクトとして取り扱えるとプログラムを書きやすいですよね。
struct ScrollDirection { /// 直前のcontentOffset let old: CGPoint /// 現在のcontentOffset let new: CGPoint /// 横スクロール方向 var directionX: ScrollDirectionX { if old.x > new.x { return .lead } else if old.x < new.x { return .tail } else { return .none } } /// 縦スクロール方向 var directionY: ScrollDirectionY { if old.y > new.y { return .top } else if old.y < new.y { return .bottom } else { return .none } } init(old: CGPoint, new: CGPoint) { self.old = old self.new = new } }
イニシャライザで新旧のcontentOffsetを渡してあげるとdirectionY
、directionX
からスクロール方向を取得できるようになります。
ScrollDirectionを生成するための処理を定義
最後にUIScrollViewを編集してスクロール方向を取得する処理を定義すれば完成です!
var scrollObserved: Bool = false override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == scrollDetectableKeyPath, let _change = change { guard let old = (_change[.oldKey] as? NSValue)?.cgPointValue, let new = (_change[.newKey] as? NSValue)?.cgPointValue else { fatalError("Undefined KVO key.") } let direction = ScrollDirection(old: old, new: new) print(direction.directionY) print(direction.directionX) } }
適当なタイミングで、enableScrollDirectionDetect
、disableScrollDirectionDetect
を呼び出してあげればいい感じにスクロール方向を取得出来るようになります。
ということで、スクロール方向をいい感じに取得する方法を考えてみました。
なんだかもっといい方法がありそうなのですけどね(^_^;)
ということで、ガラパゴスではもっといい方法でiOSプログラムを書いていける!という腕自慢のエンジニアを絶賛募集中です!
たくさんのご応募お待ちしております。
*1:良い 方法をご存知の方は是非教えて下さい!