【Swift4】多段カルーセルの実装【iOS】

マンガアプリなどのトップページでよく見かけるUIです。これをコレクションビューで実装するメモです。

完成イメージ
![bbbb.gif](https://qiita-image-store.s3.amazonaws.com/0/151314/0b465f49-590d-2ba3-0b97-af6c51e1c604.gif)

一番上と一番下のカルーセルはグリッド吸着タイプ、真ん中はフリーです。


### ビューコントローラ

```Swift:ViewController
import UIKit

class ViewController: UIViewController {
    
    var carousel1 : UICollectionView!
    var carousel2 : UICollectionView!
    var carousel3 : UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // カルーセルの高さ
        let carouselHeight:CGFloat = 200
        
        // CollectionViewのレイアウトを生成.
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        
        // カルーセル１を生成.
        carousel1 = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        carousel1.frame = CGRect(x:0, y:32, width:view.frame.size.width, height:carouselHeight)
        carousel1.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
        carousel1.backgroundColor = .clear
        carousel1.delegate = self
        carousel1.dataSource = self
        carousel1.isPagingEnabled = true // セルが吸着する
        carousel1.showsHorizontalScrollIndicator = false
        carousel1.tag = 0
        view.addSubview(carousel1)
     
        // カルーセル２を生成.
        carousel2 = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        carousel2.frame = CGRect(x:0, y:carousel1.frame.origin.y + carouselHeight + 10, width:view.frame.size.width, height:carouselHeight)
        carousel2.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
        carousel2.backgroundColor = .clear
        carousel2.delegate = self
        carousel2.dataSource = self
        carousel2.isPagingEnabled = false // セルが吸着しない
        carousel2.showsHorizontalScrollIndicator = false
        carousel2.tag = 1
        view.addSubview(carousel2)
        
        // カルーセル３を生成.
        carousel3 = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        carousel3.frame = CGRect(x:0, y:carousel2.frame.origin.y + carouselHeight + 10, width:view.frame.size.width, height:carouselHeight)
        carousel3.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
        carousel3.backgroundColor = .clear
        carousel3.delegate = self
        carousel3.dataSource = self
        carousel3.isPagingEnabled = true // セルが吸着する
        carousel3.showsHorizontalScrollIndicator = false
        carousel3.tag = 2
        view.addSubview(carousel3)
        
    }
}


extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        let pageViewRect = self.view.bounds
        return CGSize(width: pageViewRect.width, height: pageViewRect.height-80)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0.0
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 0.0
    }
}

extension ViewController: UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCollectionViewCell
        
        switch collectionView.tag
        {
        case 0:
            cell.setViewColor(.cyan)
            break
        case 1:
            cell.setViewColor(.orange)
            break
        case 2:
            cell.setViewColor(.green)
            break
        default:
            break
        }
        
        return cell
    }

}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // セルタップ時の挙動
        print("僕のタグ番号は\(collectionView.tag)です")
        print("Cellインデックスは\(indexPath.row)です")

    }
}
```
コレクションビューを３つ作成しています。コレクションビューのプロパティ**`isPagingEnabled`**をtrueにするとグリッド吸着タイプの挙動（ページング）となります。**`UICollectionViewDelegateFlowLayout`**プロトコルにてセルの余白を設定しています。isPagingEnabledの兼ね合いでここでは余白をゼロに設定しています。
データ・ソースや処理の分岐はそれぞれのコレクションビューにタグ番号をセットすることで対応しています。

### カスタムセルクラス

```Swift:MyCollectionViewCell
import UIKit

class MyCollectionViewCell: UICollectionViewCell {
    
    var baseView:UIView?
    let paddingSize:CGFloat = 8
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // コンテンツビューに対しパディングをもうけたビューを生成して貼る（ページング吸着対応）
        baseView = UIView(frame:CGRect(x:paddingSize,y:paddingSize,width:frame.width-(paddingSize*2),height:frame.height))
        self.contentView.addSubview(baseView!)
    }
    
    // ベースビューの背景色を変更する
    func setViewColor(_ color:UIColor) {
        baseView?.backgroundColor = color
    }
    
}
```
横幅いっぱいのコンテンツビューにひとまわり小さいビューを貼り付けることで余白があるように見せています。こうすることで**`isPagingEnabled`**がtrue時にもきれいにページングしてくれます。