UICollectionView の使い方（列数を指定してレイアウトする方法）

## 完成イメージ
<img width="300" alt="スクリーンショット 2019-05-21 15.39.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/85da2e3a-2171-4a09-fbf2-bd88ad9b9467.png">



## 実現したいこと
- 1行に表示するアイテムを２個（つまり２列）とする。
- 1つのセルに複数のアイテムを入れる。
- セクションの左右、セル間の余白は 2.0 とする。

（メモ）cellのクラスを作成する方法もありますが、今回はTagを使った方法で実装しています。


##手順

###1. StoryboardのView上に Library(Cmd + Shift + L)からCollectionVIew を持ってくる。
SafeAreaに対する制約をつけておく。
<img width="400" alt="スクリーンショット 2019-05-21 10.10.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/a44db6ff-2cb3-6456-2f1f-438daa044b8c.png">


###2. Storyboard上の cellを選択して、Collection Reusable View の Identifier を "Cell" とする。

<img width="400" alt="スクリーンショット 2019-05-21 10.13.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/8a108dfb-344e-b922-9a86-003c0fa719a2.png">


###3. cellにLibraryよりImageVIewを2つと、Labelを置き、それぞれに制約をつける。

<img width="417" alt="スクリーンショット 2019-05-21 15.25.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/6851ad85-4d14-ecea-705a-9b4e719f5790.png">

（参考）制約の付け方

「下部の小さいImageView」
<img width="500" alt="スクリーンショット 2019-05-21 15.19.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/77cbb23f-4794-3478-e9f1-7cdb50ba89a1.png">

「ラベル」
<img width="500" alt="スクリーンショット 2019-05-21 15.17.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/32f2fc8a-857f-62c7-c7f1-c75641924584.png">

「下部の小さいImageView」と「ラベル」をStackViewに組み込む
<img width="500" alt="スクリーンショット 2019-05-21 15.17.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/d82c0b36-d3bb-31fd-0b15-c1f277304466.png">

「上部の大きいImageView」
<img width="500" alt="スクリーンショット 2019-05-21 15.17.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/ca80830a-2ce4-54be-5016-288a7bc87b2d.png">




###4. 3.で作った2つのImageViewと、Label　それぞれのtagに番号を振る。

大きいImageView ... tag 1
小さいImageView ... tag 2
Label ... tag 3

大きいImageView↓
<img width="400" alt="スクリーンショット 2019-05-21 10.30.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/76bc1ff2-75ae-80ea-af17-3c7efad0267e.png">


ImageView の ContentMode を AspectFill に変更する。
<img width="400" alt="スクリーンショット 2019-05-21 11.35.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/7bea99c7-7455-7f5f-ca69-06088d502bcd.png">


###5. ViewController.swift のclassに、UICollectionViewDelegate, UICollectionViewDataSource を追加する。

エラーが出るので、不足しているコードを追記する。

```swift:ViewController.swift
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    private let photos = ["photo0", "photo1", "photo2", "photo3", "photo4", "photo5","photo0", "photo1", "photo2", "photo3", "photo4", "photo5"]
    private let userImages = ["userImage0", "userImage1", "userImage2", "userImage3", "userImage4", "userImage5","userImage0", "userImage1", "userImage2", "userImage3", "userImage4", "userImage5"]
    private let titles = ["パンケーキ", "ラーメン", "サンドウィッチ", "人参とポテト", "プレートランチ", "サーモングリル", "パンケーキ", "ラーメン", "サンドウィッチ", "人参とポテト", "プレートランチ", "サーモングリル"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    

    // １つのセクションの中に表示するセル（要素）の数。
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photos.count
    }

　　　// セル（要素）に表示する内容
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // "Cell" の部分は　Storyboard でつけた cell の identifier。
        let cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        
        // Tag番号を使ってインスタンスをつくる
        let photoImageView = cell.contentView.viewWithTag(1)  as! UIImageView
        let photoImage = UIImage(named: photos[indexPath.row])
        photoImageView.image = photoImage
        
        let userImageView = cell.contentView.viewWithTag(2)  as! UIImageView
        let userImage = UIImage(named: userImages[indexPath.row])
        userImageView.image = userImage
        
        let titleLabel = cell.contentView.viewWithTag(3) as! UILabel
        titleLabel.text = titles[indexPath.row]
        
        return cell
    }
```


###6. Storyboard に戻り、CollectionView と View Controller を紐づけする。
CollectionView を選択して cintrol を押しながら、ViewControllerにドラッグする。
dataSource、delegate
とそれぞれ結び紐付けします。

<img width="279" alt="スクリーンショット 2019-05-21 11.07.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/ace57d9f-c023-73cd-048d-c0eb768e5ed5.png">
<img width="252" alt="スクリーンショット 2019-05-21 11.08.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/12b73928-d243-5644-7144-7e43f326f5eb.png">


###7. photo0...と、userImage0...用に、適当な画像をAssets.xcassets に保存しておく。

###8. 実行

<img width="300" alt="スクリーンショット 2019-05-21 15.33.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/4ca2b707-2adb-e856-1fc4-ed71b0c6cea7.png">

とりあえず表示することころまではできたので、レイアウトを整えていく。

###9. レイアウトを整える。

ViewController.swift のclassに、UICollectionViewDelegateFlowLayout を追加する。

以下をViewController.swift に追記する。

```swift:ViewController.swift

    // レイアウト設定　UIEdgeInsets については下記の参考図を参照。
    private let sectionInsets = UIEdgeInsets(top: 10.0, left: 2.0, bottom: 2.0, right: 2.0)
    // 1行あたりのアイテム数
    private let itemsPerRow: CGFloat = 2


```


```swift:ViewController.swift

    // Screenサイズに応じたセルサイズを返す
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
        let availableWidth = view.frame.width - paddingSpace
        let widthPerItem = availableWidth / itemsPerRow
        return CGSize(width: widthPerItem, height: widthPerItem + 42)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }

    // セルの行間の設定
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10.0
    }

```



<img width="485" alt="スクリーンショット 2019-05-21 13.50.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/088b83db-fa6d-9757-57de-ce2e018e3609.png">
参考図：[Apple公式リファレンス　Collection View Programming Guide for iOS](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html)より

(メモ) 実行してみて、1列の表示なったら下記を確認
collectionView のMin Spacing に値が入っていたら 0 にする。

<img width="256" alt="スクリーンショット 2019-05-21 14.19.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/c881c77e-a9b3-4882-7f27-ff559ac7c5b5.png">

###10. セルがタップされたときの処理

```swift:ViewController.swift

    // セルが選択されたときの処理
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("\(titles[indexPath.row])がtapされたよ")
    }
```

###11. 完成！
<img width="300" alt="スクリーンショット 2019-05-21 15.39.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/6cd5524d-de0f-dfa9-d5fb-ca5cef8899d4.png">

###おまけ...
３列表示にしてみるとこんな感じに。いろいろ応用できそうです。

```swift

    // 1行あたりのアイテム数を3にする
    private let itemsPerRow: CGFloat = 3
```

<img width="300" alt="スクリーンショット 2019-05-21 15.41.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118372/b16a5806-4959-7303-db38-6a97ad753307.png">

###コード全体
```swift:ViewController.swift
import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    private let photos = ["photo0", "photo1", "photo2", "photo3", "photo4", "photo5","photo0", "photo1", "photo2", "photo3", "photo4", "photo5"]
    private let userImages = ["userImage0", "userImage1", "userImage2", "userImage3", "userImage4", "userImage5","userImage0", "userImage1", "userImage2", "userImage3", "userImage4", "userImage5"]
    private let titles = ["パンケーキ", "ラーメン", "サンドウィッチ", "人参とポテト", "プレートランチ", "サーモングリル", "パンケーキ", "ラーメン", "サンドウィッチ", "人参とポテト", "プレートランチ", "サーモングリル"]
    
    // レイアウト設定
    private let sectionInsets = UIEdgeInsets(top: 10.0, left: 2.0, bottom: 2.0, right: 2.0)
    
    // 1行あたりのアイテム数
    private let itemsPerRow: CGFloat = 2

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    

    // 要素の数。
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photos.count
    }
    // セル（要素）に表示する内容
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // "Cell" の部分は　Storyboard でつけた cell の identifier。
        let cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        
        // Tag番号を使ってImageViewののインスタンスをつくる
        let photoImageView = cell.contentView.viewWithTag(1)  as! UIImageView
        let photoImage = UIImage(named: photos[indexPath.row])
        photoImageView.image = photoImage
        
        
        let userImageView = cell.contentView.viewWithTag(2)  as! UIImageView
        let userImage = UIImage(named: userImages[indexPath.row])
        userImageView.image = userImage
        
        let titleLabel = cell.contentView.viewWithTag(3) as! UILabel
        titleLabel.text = titles[indexPath.row]
        
        return cell
        
    }
    
    // Screenサイズに応じたセルサイズを返す
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
        let availableWidth = view.frame.width - paddingSpace
        let widthPerItem = availableWidth / itemsPerRow
        return CGSize(width: widthPerItem , height: widthPerItem + 42)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }
    // セルの行間の設定
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10.0
    }

    // セルが選択されたときの処理
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("\(titles[indexPath.row])がtapされたよ")
    }

}

```


### （参考）
- [Apple公式リファレンス　Collection View Programming Guide for iOS](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html)

- [iPhone アプリ開発](https://i-app-tec.com/ios/collectionview.html)
- [UICollectionView Tutorial: Getting Started]    (https://www.raywenderlich.com/9334-uicollectionview-tutorial-getting-started
)

- App icon by [icons8](https://icons8.jp/)
- App photo by [pexels](https://www.pexels.com/ja-jp/photo-license/)