LoginSignup
22
10

More than 5 years have passed since last update.

コードでカスタムセルを使う時register(_:forCellReuseIdentifier:)関数を入れそびれていた

Last updated at Posted at 2018-05-16

前提

Xcode9.2
Swift4

問題

列ごとに表示するセルを決めるデリゲートメソッドを書いて、その列にどのセルを使うかというのを決めると思います

extension ListViewController: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(ListTableViewCell.self), for: indexPath) as? ListTableViewCell else {
            fatalError("The dequeued cell is not instance of MealTableViewCell.")
        }

///ここ以下省略

ここで落ちてしまう

guard let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(ListTableViewCell.self), for: indexPath) as? ListTableViewCell

エラーメッセージ

reason: 'unable to dequeue a cell with identifier Todolist_firebase.MyCell - 
must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

原因と解決策

下記の入れ忘れ

tableView.register(ListTableViewCell.self, forCellReuseIdentifier: NSStringFromClass(ListTableViewCell.self))

register(_,identifier)のApple公式ドキュメントではここで紹介されています

func register(_ cellClass: AnyClass?, 
forCellReuseIdentifier identifier: String)
セルを作成する前に、このメソッド(_:forCellReuseIdentifier :)を呼び出して、新しいセルをつくることをテーブルビューに伝えます。 指定されたタイプのセルが現在再利用キューにない場合、テーブルビューは提供された情報を使用して新しいセルオブジェクトを自動的に作成します。

つまり第一引数にはこのテーブルビューで使うcellのクラスを登録し、第二引数では再利用する際の名札的な立ち位置でString型の名前を入れてねということなのでしょう

dequeueReusableCell(withIdentifier:for:)のアップル公式のドキュメント
再利用可能なテーブルビューセルオブジェクトを返し、それをテーブルに追加する関数ですね
この関数は第一引数がString型のidentifierで第二引数がindexPathなので何番目のセルを対象としているのかという数字が返ってきます
第一引数に着目すると型はString型になっており、上記のregister関数でstringで登録したものを再利用できるようにしているのですね

func dequeueReusableCell(withIdentifier identifier: String, 
                     for indexPath: IndexPath) -> UITableViewCell

公式ドキュメントではわざわざ黄色く囲まれたImportantのところで
screenshot.png

重要

このメソッドを呼び出す前に、register(_:forCellReuseIdentifier :)またはregister(_:forCellReuseIdentifier :)メソッドを使用してクラスまたはnibファイルを登録する必要があります。

と書いてありました

コード全体

一部省略してます
ちなみに何故register関数の第二引数でNSStringFromClass(ListTableViewCell.self)このようにしているかというと、NSStringFromClass関数は引数で入れたクラスをStringにして返してくれる関数だからです

例
NSStringFromClass(ListTableViewCell.self)
↓
"Todolist_firebase.ListTableViewCell"
class ListViewController: UIViewController {
    var tableView = UITableView()
    override func viewDidLoad() {
        super.viewDidLoad()

        //テーブルビュー
        tableView = UITableView(frame: self.view.frame, style: .plain)
        tableView.rowHeight = 100
        tableView.delegate = self
        tableView.dataSource = self
        //NSStrignFromClassはクラスの名前をStringで返してくれる
        tableView.register(ListTableViewCell.self, forCellReuseIdentifier: NSStringFromClass(ListTableViewCell.self))
        self.view.addSubview(tableView)
    }

}

extension ListViewController: UITableViewDelegate, UITableViewDataSource {

    //セクションの数を設定
    func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    //行数 セクションによって行数が違う場合はsectionで場合分けをするUITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    //列ごとに表示するセルを決めるデリゲートメソッド
    //UITableViewDataSource は主に Table View が表示するデータを与えるものです。
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(ListTableViewCell.self), for: indexPath) as? ListTableViewCell else {
            fatalError("The dequeued cell is not instance of MealTableViewCell.")
        }

        let item = items[indexPath.row]
        cell.nameLabel.text = item.title     
        return cell
    }
}

class ListTableViewCell: UITableViewCell {

    //MARK: Properties
    var nameLabel: UILabel!

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.commonInit() 
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepareForReuse() {
        super.prepareForReuse()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.addSubview(nameLabel)
        nameLabel.frame = CGRect(x: 110, y: 0, width: frame.width - 100, height: frame.height)

    }

    //MARK : method
    private func commonInit() {
        self.createNameLabel()
    }
    private func createNameLabel() {
        nameLabel = UILabel(frame: CGRect.zero)
        nameLabel.textAlignment = .left
        nameLabel.font = UIFont.systemFont(ofSize: 20)
    }


}

おまけ

Appleのドキュメント曰く

指定した識別子のクラスを登録し、新しいセルを作成する必要がある場合、init(style:reuseIdentifier :)メソッドを呼び出してセルを初期化します。 
既存のセルが再利用可能な場合、このメソッドは代わりにセルのprepareForReuse()メソッドを呼び出します。

とのことで、prepareForReuse()関数がいつ使われるのかがドキュメントを確認することでわかりました

22
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
10