読者です 読者をやめる 読者になる 読者になる

Storyboardを使ってUITableViewを組み立てる場合のテンプレート(Swift3)

環境: Swift3

よく使うのでコピペ用にメモしておく。

ViewController

import UIKit

class ViewController: UIViewController {

    var items: [String] = ["foo", "bar", "hoge"]
    
    @IBOutlet weak var tableView: UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
        cell.item = self.items[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    
}

final class MyCell: UITableViewCell {
    
    var item: String? {
        didSet {
            self.nameLabel?.text = self.item
        }
    }

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

    @IBOutlet weak var nameLabel: UILabel?
}

カスタムセルMyCellを定義する。
Storyboard 上でカスタムセルを貼り付けて定義している場合は以下のような register メソッドは不要。
逆にregister メソッドで登録してしまうとカスタムセルが表示されなくなってしまうので注意。
カスタムセルをコードのみで定義、または別途 nib ファイルで定義した場合は viewDidLoad などで以下のように register メソッドで登録する。

self.tableView?.register(MyCell.self, forCellReuseIdentifier: "MyCell")
self.tableView?.register(UINib(nibName: "MyCell", bundle: nil), forCellReuseIdentifier: "MyCell")

また、普通やらないと思うが、カスタムセルを使う場合に UITableViewCell にデフォルトで用意されているプロパティ(textLabelなど)を使うと表示がおかしくなるのでやらないこと。

Storyboard

UIViewController に UITableView と UITableViewCell を貼り付ける。
UITableView の datasource と delegate を UIViewController に接続する。
UITableViewCell のクラス名を設定、Identifier を設定。
UITableViewCell 上にラベルなどがあればそれも接続する。

f:id:xyk:20170115202101p:plain f:id:xyk:20170115202043p:plain

Extension

Cell・HeaderFooterViewのregisterやdequeueのIdentifierは文字列で扱うが、大抵はクラス名をそのまま使用するので、文字列ではなく、クラスを使って扱えるようにExtensionを定義する。

定義

extension UITableView {

    // func dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell
    // の代わりに使用する
    func dequeueReusableCell<T: UITableViewCell>(withClass type: T.Type, for indexPath: IndexPath) -> T {
        return self.dequeueReusableCell(withIdentifier: String(describing: type), for: indexPath) as! T
    }

    // func dequeueReusableHeaderFooterView(withIdentifier identifier: String) -> UITableViewHeaderFooterView?
    // の代わりに使用する
    func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(withClass type: T.Type) -> T {
        return self.dequeueReusableHeaderFooterView(withIdentifier: String(describing: type)) as! T
    }

    // func register(_ nib: UINib?, forCellReuseIdentifier identifier: String)
    // func register(_ cellClass: Swift.AnyClass?, forCellReuseIdentifier identifier: String)
    // の代わりに使用する
    func register(tableViewCellClass cellClass: AnyClass) {
        let className = String(describing: cellClass)
        if UINib.fileExists(nibName: className) {
            self.register(UINib.cachedNib(nibName: className), forCellReuseIdentifier: className)
        } else {
            self.register(cellClass, forCellReuseIdentifier: className)
        }
    }

    // func register(_ nib: UINib?, forHeaderFooterViewReuseIdentifier identifier: String)
    // func register(_ aClass: Swift.AnyClass?, forHeaderFooterViewReuseIdentifier identifier: String)
    // の代わりに使用する
    func register(headerFooterViewClass aClass: AnyClass) {
        let className = String(describing: aClass)
        if UINib.fileExists(nibName: className) {
            self.register(UINib.cachedNib(nibName: className), forHeaderFooterViewReuseIdentifier: className)
        } else {
            self.register(aClass, forHeaderFooterViewReuseIdentifier: className)
        }
    }
}


extension UINib {

    static let nibCache = NSCache<NSString, UINib>()

    static func fileExists(nibName: String) -> Bool {
        return Bundle.main.path(forResource: nibName, ofType: "nib") != nil
    }

    static func cachedNib(nibName: String) -> UINib {
        if let nib = self.nibCache.object(forKey: nibName as NSString) {
            return nib
        } else {
            let nib = UINib(nibName: nibName, bundle: nil)
            self.nibCache.setObject(nib, forKey: nibName as NSString)
            return nib
        }
    }
}

使用時

// Cellの登録
// tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
tableView.register(tableViewCellClass: MyCell.self)

// Cellの取得
// let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
let cell = tableView.dequeueReusableCell(withClass: MyCell.self, for: indexPath)

// HeaderFooterViewの登録(MyHeaderView.nibを使用)
// let className = String(describing: MyHeaderView.self)
// tableView.register(UINib(nibName: className, bundle: nil), forCellReuseIdentifier: className)
tableView.register(headerFooterViewClass: MyHeaderView.self)

// HeaderFooterViewの取得
// let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "MyHeaderView") as! MyHeaderView
let view = tableView.dequeueReusableHeaderFooterView(withClass: MyHeaderView.self)