【iOS】無限スクロールするUICollectionViewの作り方

## はじめに

iOS Second Stage Advent Calendar 13日目の記事です。

今回で4記事目です。

宜しくお願いします。

## 本題

タイトル通り、無限スクロールする`UICollectionView`です。

![SampleInfinityCollectionView.gif](https://qiita-image-store.s3.amazonaws.com/0/47302/df031124-166d-f5e5-dbd2-4e9d1aa09581.gif)

~~なんかGIFが載せられないので後日載せます。~~

プロジェクトは以下にあるので、興味ある方はみてください。

[AdventCalendar2015/SampleInfinityCollectionView at master · ryokosuge/AdventCalendar2015](https://github.com/ryokosuge/AdventCalendar2015/tree/master/SampleInfinityCollectionView)

## ちょっとした解説

`UICollectionView`の`layoutSubviews()`でちょっと手を加えてます。

```swift:InfinityCollectionView.swift
import UIKit

class InfinityCollectionView: UICollectionView {
    
    /// 表示するCellの数を増大させる比率
    var factor: Int = 1
    private var needsScrollToCenter: Bool = true

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        
        layoutRecenter()
        
        if contentSize.width > 0.0 && needsScrollToCenter {
            var i: Int = 0
            var x: CGFloat = 0.0
            while i <= 0 {
                let indexPath = NSIndexPath(forItem: i, inSection: 0)
                guard let attribute = collectionViewLayout.layoutAttributesForItemAtIndexPath(indexPath) else {
                    break
                }
                var minimumInteritemSpacing: CGFloat = 0.0
                if let delegateFlowLayout = delegate as? UICollectionViewDelegateFlowLayout {
                    minimumInteritemSpacing = delegateFlowLayout.collectionView?(self, layout: self.collectionViewLayout, minimumInteritemSpacingForSectionAtIndex: 0) ?? 0.0
                } else if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
                    minimumInteritemSpacing = flowLayout.minimumInteritemSpacing
                }
                x += attribute.bounds.width - (minimumInteritemSpacing * 2)
                i++
            }
            setContentOffset(CGPoint(x: contentOffset.x - x, y: 0), animated: false)
            needsScrollToCenter = false
        }
        
    }
}

extension InfinityCollectionView {
    
    private func layoutRecenter() {
        let currentOffset = contentOffset
        let contentWidth = contentSize.width
        let centerOffsetX = contentWidth / 2.0
        
        let distanceFromCenterX = fabs(currentOffset.x - centerOffsetX)
        
        let allCellWidth = CGFloat(contentWidth / CGFloat(factor))
        if distanceFromCenterX > allCellWidth {
            let offset = CGFloat(fmodf(Float(currentOffset.x - centerOffsetX), Float(allCellWidth)))
            contentOffset = CGPoint(x: centerOffsetX + offset, y: currentOffset.y)
        }
    }
    
}
```

要は`scrollOffset`の値が真ん中以上だったらちょっと同じ要素のCellの位置までOffsetを戻して更新しているという感じです。

すごくシンプルな話なので、深く考えなくて大丈夫かと思います。

## 疑問

できれば普通に`UICollectionView`を使うみたいにやりたかったのですが、`UICollectionView`の`delegate`とか`dataSource`とかをOverrideするやり方が **Swift**だとわかりませんでした...。

どなたかやり方とかURLとか教えてもらえるとすごく嬉しいです。

オープンソース、見てみます。

## 終わりに

できることの知見を残しておきたいなと思い、色々サンプルを作っています。

結構サンプル作るの楽しいです。

おかげでGitHubにも草が生えてきました。

以上です。