minneってアプリのトップ画面にあるようなやつ。動画を貼りたかったけどQiitaは動画無理そうだったのでDownloadして確認してみてほしい(宣伝)。
![Image from iOS (2).png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F17652%2F89afed4e-c71c-82e5-c635-7152967636eb.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f3ce2d23edd25e8ae6906ca765b20f03)
この画面の横スクロールする箇所(画像でいうと「minne'sセレクト」と「読みもの」)は、UICollectionViewで実装しているのですが、スクロールが終わる際にセルがちょうどよく左端に止まるように作っています。
2年半くらい前にこれを実装したのですが、最近別のアプリで同じようなことしようと思ってコードを見返してみたので、そのときに考えていたことを思い出しつつ、ついでにメモしておきます。
実装
UICollectionViewFlowLayout
をカスタマイズする形でやる。各種数値は仮。
class CustomFlowLayout: UICollectionViewFlowLayout {
private let largeFlickVelocityThreshold: CGFloat = 2.0
private let minimumFlickVelocityThreshold: CGFloat = 0.2
private let pageWidth: CGFloat = 320.0
override init() {
super.init()
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
itemSize = CGSize(width: 320.0, height: 240.0)
minimumInteritemSpacing = 16.0
minimumLineSpacing = 16.0
sectionInset = UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 16.0)
scrollDirection = .horizontal
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else {
return proposedContentOffset
}
let pageWidth = itemSize.width + minimumLineSpacing
let currentPage = collectionView.contentOffset.x / pageWidth
if abs(velocity.x) > largeFlickVelocityThreshold {
// velocityが大きいときは2つ動かす
let nextPage = (velocity.x > 0.0) ? ceil(currentPage) + 1 : floor(currentPage) - 1
return CGPoint(x: nextPage * pageWidth, y: proposedContentOffset.y)
} else if abs(velocity.x) > minimumFlickVelocityThreshold {
// 1つ動かす
let nextPage = (velocity.x > 0.0) ? ceil(currentPage) : floor(currentPage)
return CGPoint(x: nextPage * pageWidth, y: proposedContentOffset.y)
} else {
// velocityが小さすぎるときは近いほうへ
return CGPoint(x: round(currentPage) * pageWidth, y: proposedContentOffset.y)
}
}
}
minneのコードや今担当しているプロダクトで書いてるコードでは、上記をベースにセルのサイズや一度のスクロールで動かすセルの数などをカスタマイズできるようにしています。
大きくスワイプしたとき(velocityの値が大きいとき)は大きく進めるようにしないと、意図に反した動きに感じてしまう。ここらへんの調整はちょうどいいポイントを見つけるのが難しいので、周りの人に触ってもらって意見をもらうとよい。
補足
最初は手間をかけずに普通のスクロールで実装したのだけど、チームメンバーから違和感があるというレビューを受けてこの実装に至った記憶があります。実際にデフォルトの状態で横スクロールを実装するとなんか気持ち悪さがある。たぶん横の動きはPageViewControllerに慣れているからその動きを期待しちゃうのかな。
注意点として、こうしたインタラクションのカスタマイズは下手にやると自己帰属感が失われてしまい、逆に違和感を生んでしまう可能性があります。作ったらいろんな人に触ってもらってフィードバックをもらい、改善していくとよさそう。