UIScrollView(あるいはUITableView、UICollectionView)を一番上あるいは一番下までスクロールさせたいときがあります。 そんな時に役に立つextensionを紹介します。
実装
こんな感じでUIScrollViewのextensionを実装します。
extension UIScrollView {
public enum ScrollDirection {
case top
case bottom
case left
case right
}
public func scroll(to direction: ScrollDirection, animated: Bool) {
let offset: CGPoint
switch direction {
case .top:
offset = CGPoint(x: contentOffset.x, y: -contentInset.top)
case .bottom:
offset = CGPoint(x: contentOffset.x, y: max(-contentInset.top, contentSize.height - frame.height + contentInset.bottom))
case .left:
offset = CGPoint(x: -contentInset.left, y: contentOffset.y)
case .right:
offset = CGPoint(x: max(-contentInset.left, contentSize.width - frame.width + contentInset.right), y: contentOffset.y)
}
setContentOffset(offset, animated: animated)
}
}
ポイントとしては、 contentInset
を考慮してあげることと、UIViewの座標系から見て、下や右の場合には、スクロール可能かどうかを判断する必要があります。
contentInsetを考慮する
例えば、一番上にスクロールする時に、以下のように単純に y = 0.0
としてしまうと、
contentInset.top
が0以外の場合に、正しい位置に戻ることができません。
let offset = CGPoint(x: contentOffset.x, y: 0.0) // これは誤り
setContentOffset(offset, animated: animated)
なので、一番上にスクロールする場合は、 y = -contentInset.top
として、inset分引いてあげます。
let offset = CGPoint(x: contentOffset.x, y: -contentInset.top)
setContentOffset(offset, animated: animated)
逆に、一番下にスクロールする場合は、 contentInset.bottom
分足してあげます。
// 注意:これはまだ完全なものでは無いです。
let offset = CGPoint(x: contentOffset.x, y: contentSize.height - frame.height + contentInset.bottom)
setContentOffset(offset, animated: animated)
このように、 contentSize.height - frame.height
をした値に、inset分足して上げて調整します。
しかし、これだと contentSize.height - frame.height < 0
となってしまうと、
本来不要なスクロールをしてしまいます。
スクロール可能かどうかも考慮する
contentSize.height - frame.height < 0
の場合には y = 0
となるようにします。
そこで max 関数を使用します。さらに、 contentInset.top
の値も考慮してあげるとこのようになります。
let offset = CGPoint(x: contentOffset.x, y: max(-contentInset.top, contentSize.height - frame.height + contentInset.bottom))
setContentOffset(offset, animated: animated)
これで contentSize.height - frame.height < -contentInset.top
となる場合はスクロールしないように制御できます。
あとは、左/右の場合も上/下と同様に実装してあげます。
使用方法
こんな感じでDirectionと、アニメーションさせるかどうかを与えてあげます。
// 一番上にスクロール
tableView.scroll(to: .top, animated: true)
// 一番下にanimated=falseでスクロール
tableView.scroll(to: .bottom, animated: false)
スッキリしました。
追記
一応gistにも書いておきました。