56
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

UICollectionViewのカスタムセルの作り方とセルの再利用について

UICollectionViewのカスタムセルの作り方をご紹介します。
また、UICollectionViewの効率的な処理を支えるセルの再利用について、その仕組みおよびカスタムセル実装時の注意点についても触れます。

カスタムセルを利用したUICollectionViewの実装

最終的に作成する画面は以下のようなものです。
UILabelを縦に2つ並べただけのセルです。
後半のセルの再利用の仕組みについて説明するのに都合の良い画面になってます:sweat_smile:

image.png

StoryBoardの設定

まずはStoryBoardから。
ViewControllerにUICollectionViewを配置します。
UICollectionViewには上下左右にSafeAreaに対する制約をつけています。
UICollectionView内のセルはここでは削除しておきます。

image.png

カスタムセル用のXIBファイルの作成

次はカスタムセルのxibファイルです。
XCodeメニューのFile > New > File...とたどり、Cocoa Touch Classを選択します。

image.png

クラスの型をUICollectionViewCellにして、名前をつけます(ここではCustomCellという名前にします)。
Also create XIB fileにチェックを付けておいてください。

image.png

CustomCell.xibというファイルが作成されたら、適当にUILabelを2つ配置した以下のような画面を作成します。

image.png

ViewControllerの実装

続いてViewControllerの実装です。

collectionView.register(UINib(nibName: "CustomCell", bundle: nil), forCellWithReuseIdentifier: "CustomCell")

まず、コレクションビューで使用するセルをregister(_:forCellWithReuseIdentifier:)メソッドで登録します。
第一引数にはUINibオブジェクトを渡します。
UINibのイニシャライザに渡しているCustomCellという文字列は、セルのXIBファイル名と一致します。
第二引数のforCellWithReuseIdentifierには、セルの再利用識別子というものを指定します。
ここの文字列は任意ですが、ここではCustomCellを指定します。

続いてデータソースの実装です。
ViewControllerにUICollectionViewDataSourceプロトコルを実装します。

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return models.count
}

collectionView(_:numberOfItemsInSection:)メソッドでは、コレクションビューに配置するセルの個数を返すようにします。
modelsはセルに表示するデータを持つモデルオブジェクトの配列です。
モデルオブジェクトについてはあとで触れます。

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath)

    if let cell = cell as? CustomCell {
        cell.setupCell(model: models[indexPath.row])
    }

    return cell
}

collectionView(_:cellForItemAt:)メソッドでは、セルの設定を行います。
dequeueReusableCell(withReuseIdentifier:for:)メソッドでセルオブジェクトを取り出し、セルにモデルオブジェクトを渡して設定を行っています。
新しくCustomCellオブジェクトを生成するのではなく、dequeueReusableCell(withReuseIdentifier:for:)メソッドでキューに溜まっているセルオブジェクトを再利用するというところがポイントです。
あとで詳しく触れます。

全体のViewControllerの実装は以下のとおりです。

ViewController.swift
import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!

    let models = Model.createModels()

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.dataSource = self

        collectionView.register(UINib(nibName: "CustomCell", bundle: nil), forCellWithReuseIdentifier: "CustomCell")

        // セルの大きさを設定
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: collectionView.frame.width, height: 100)
        collectionView.collectionViewLayout = layout
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return models.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath)

        if let cell = cell as? CustomCell {
            cell.setupCell(model: models[indexPath.row])
        }

        return cell
    }
}

カスタムセルクラスの実装

カスタムセルのクラスの実装は以下のとおりです。

CustomCell.swift
import UIKit

class CustomCell: UICollectionViewCell {
    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var subTitle: UILabel!

    func setupCell(model: Model) {
        title.text = model.title

        if let text = model.subTitle {
            subTitle.text = text
        }

        self.backgroundColor = .lightGray
    }
}

titlesubTitleはXIBファイル上のUILabelオブジェクトとリンクしています。
setupCell(model:)メソッドはモデルオブジェクトを受け取って、2つのラベルの設定を行います。

モデルクラスの実装

最後にモデルのクラスです。

Model.swift
struct Model {
    let title: String
    let subTitle: String?

    static func createModels() -> [Model] {
        return [
            Model(title: "AAAAAAAAAA", subTitle: "aaaaaaaaaa"),
            Model(title: "BBBBBBBBBB", subTitle: "bbbbbbbbbb"),
            Model(title: "CCCCCCCCCC", subTitle: "cccccccccc"),
            Model(title: "DDDDDDDDDD", subTitle: "dddddddddd"),
            Model(title: "EEEEEEEEEE", subTitle: "eeeeeeeeee"),
            Model(title: "FFFFFFFFFF", subTitle: "ffffffffff"),
            Model(title: "GGGGGGGGGG", subTitle: "gggggggggg"),
            Model(title: "HHHHHHHHHH", subTitle: "hhhhhhhhhh"),
            Model(title: "IIIIIIIIII", subTitle: "iiiiiiiiii"),
            Model(title: "JJJJJJJJJJ", subTitle: "jjjjjjjjjj"),
            Model(title: "KKKKKKKKKK", subTitle: "kkkkkkkkkk"),
            Model(title: "LLLLLLLLLL", subTitle: "llllllllll"),
            Model(title: "MMMMMMMMMM", subTitle: "mmmmmmmmmm"),
            Model(title: "NNNNNNNNNN", subTitle: nil),
            Model(title: "OOOOOOOOOO", subTitle: nil),
            Model(title: "PPPPPPPPPP", subTitle: nil),
            Model(title: "QQQQQQQQQQ", subTitle: nil),
            Model(title: "RRRRRRRRRR", subTitle: nil),
            Model(title: "SSSSSSSSSS", subTitle: nil),
            Model(title: "TTTTTTTTTT", subTitle: nil),
            Model(title: "UUUUUUUUUU", subTitle: nil),
            Model(title: "VVVVVVVVVV", subTitle: nil),
            Model(title: "WWWWWWWWWW", subTitle: nil),
            Model(title: "XXXXXXXXXX", subTitle: nil),
            Model(title: "YYYYYYYYYY", subTitle: nil),
            Model(title: "ZZZZZZZZZZ", subTitle: nil),
        ]
    }
}

モデルはtitlesubTitleという2つのプロパティを持っています。
subTitleはオプショナル型なので、nilの可能性もあります。
createModels()はコレクションビューに表示するデータを作成します。
ViewControllerはこのメソッドで作成したデータをmodelsプロパティに保持しています。

これらの実装でアプリを起動すると、以下のような画面になるはずです。

image.png

セルの再利用の仕組みについて

上で紹介したアプリはかなり作為的な実装になってますが、セルの再利用の仕組みについて説明するのに都合が良いのです。

上記アプリの問題点

画面を下にスクロールしていくと、ある時点からおかしなことが起こります。

image.png

NNNNNNNNNNというタイトルの下に、aaaaaaaaaaというサブタイトルが表示されています。
ViewControllerのmodelsには、Model(title: "NNNNNNNNNN", subTitle: nil)というデータが入っているので、サブタイトルには何も表示されないのが期待している動作です。

セルは再利用されている

画面を下にスクロールしていくと、上にあるセルは表示されなくなり、下から新しいセルが表示されます。
この時、コレクションビューは新しいセルオブジェクトを作成するのではなく、表示されなくなったセルを内部的なキューに入れておき、新しいセルが表示されるときにキューからセルを取り出すという方法を取ります。

記事の最初の方で触れたdequeueReusableCell(withReuseIdentifier:for:)というメソッドは、キューに溜まっている使われなくなったセルを取り出すためのものなのです。
キューはセルの種類ごとに用意されていて、再利用識別子を使ってどのキューからセルを取り出すかを決定します。

NNNNNNNNNNというタイトルの下にaaaaaaaaaaというサブタイトルが表示されていたのは、最初にAAAAAAAAAAというタイトルを表示していたセルを再利用したからです。

セルは初期化してから使用する

セルを使用するときに毎回初期化を行うことで、このようは問題はなくなります。
UICollectionViewCellにはprepareForReuse()というメソッドが用意されていて、これはセルがキューから取り出される際に呼ばれます。
以下のようにtitlesubTitleに空文字列をセットしてあげることで、期待した動作になります。

override func prepareForReuse() {
    super.prepareForReuse()

    title.text = ""
    subTitle.text = ""
}

image.png

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
56
Help us understand the problem. What are the problem?