【Swift】CollectionViewで(横)スクロールする度に位置を自動調整させる方法

# はじめに
`collectionView`で画像を横スクロールで表示させる実装をした際、中途半端な位置で止まった時に自動スクロールで位置調整させたくてたまりませんでした。

こんなイメージ
![qiitaCollectionScroll.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/4d050a1a-9a30-e3d9-0d94-89e3c70ee1bd.gif)



やってみました。

# 環境
Xcode 13.2.1
Swift 5.5.2

# 実装


**メイン画面**

```swift:ViewController
final class LessonImageViewController: UIViewController {
    @IBOutlet weak var customCollectionView: UICollectionView!
    // スクロール開始した位置
    var scrollBeginingPoint: CGPoint!
    // スクロールした方向
    var scrollDirection: Bool = true

    override func viewDidLoad() {
        super.viewDidLoad()
        
        customCollectionView.delegate = self
        customCollectionView.dataSource = self
        customCollectionView.register(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCell")

        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        customCollectionView.setCollectionViewLayout(layout, animated: true)
    }
}

extension LessonImageViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return lessonsArray.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
        guard let imageCell = customCell as? ImageCollectionViewCell else { return customCell }

        // カスタムセルを作ってセル自体にインデックスを持たせます
        imageCell.setLessonData(lesson: viewModel.lessonsArray.value[indexPath.row], row: indexPath.row)
        return imageCell
    }

    // 画面のスクロールを開始した位置を保存
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        scrollBeginingPoint = scrollView.contentOffset;
    }

    // 画面をスクロールした瞬間の位置を保存
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let currentPoint = scrollView.contentOffset;
        // スクロール開始位置とスクロールした直後の位置を比較
        // スクロールした方向を判定
        if scrollBeginingPoint.x < currentPoint.x {
            scrollDirection = true
        } else {
            scrollDirection = false
        }
    }

    // 画面のスクロールが止まったら自動スクロールさせる
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        // 画面に表示されているセルを取得
        let cells = customCollectionView.visibleCells
        var indexArray: [Int] = []
        var indexRow: Int?
        // カスタムセルに持たせておいたインデックスを取得しておく
        for cell in cells {
            guard let safeCell = cell as? ImageCollectionViewCell else { return }
            indexArray.append(safeCell.row ?? 0)
        }
        guard !indexArray.isEmpty else { return }
        // スクロール方向によって取得したインデックス番号の最大値or最小値を取得する
        if scrollDirection {
            indexRow = indexArray.max()
        } else {
       　　    indexRow = indexArray.min()
        }
        // 最後に取得したインデックスまで自動スクロールさせる
        customCollectionView.scrollToItem(at: IndexPath(item: indexRow ?? 0, section: 0), at: .centeredHorizontally, animated: true)
    }
}

```

**カスタムセルの実装**

```swift:CollectionViewCell
class ImageCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var lessonImageView: UIImageView!
    var lesson: Lesson?
    var row: Int?
    
    func setLessonData(lesson: Lesson, row: Int) {
        self.lesson = lesson
        titleLabel.text = lesson.title
        lessonImageView.contentMode = .scaleAspectFit
        lessonImageView.image = lesson.getImage()
        // セルのインデックスを持たせておく
        self.row = row
    }
}
```

- 横スクロールの方向（右or左）を判定
- スクロールが止まった時に画面に表示されているセルのインデックスを取得
- スクロール方向により適当なセルへ自動スクロールさせる

ということでした。

# 感想
無事、自動で位置調整させたくてたまらない欲求がおさまりました。
もっと簡単に実装できるような気もしていましたが、
やりたかったことが実現できたので良しとしました。


# 参考記事
https://qiita.com/osamu1203/items/a96a5fb93a5bf1614e7c

https://exgyaruo.com/programming/swift/uitableview-uicollectionview-scroll-stop