以前にもTableViewCellの高さ自動調整について書いたけど、今度はStoryboard、autolayoutを使わずにSwiftコードだけでやってみた。
いくつかの手順に分けて紹介していきます。
カスタムセルクラスの作成
例として、UILabelを配置したカスタムセルを定義する。
import UIKit class CustomListCell: UITableViewCell{ static let nameLabelFrame = CGRect(x: 10, y: 10, width: 300, height: 0) let nameLabel = UILabel(frame: CustomListCell.nameLabelFrame) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) nameLabel.numberOfLines = 0 nameLabel.lineBreakMode = .byWordWrapping self.contentView.addSubview(nameLabel) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
後述するCellの高さ自動調節をするため、
static let nameLabelFrame = CGRect(x: 10, y: 10, width: 300, height: 0) let nameLabel = UILabel(frame: CustomListCell.nameLabelFrame)
nameLabel.numberOfLines = 0 nameLabel.lineBreakMode = .byWordWrapping
とし、width:300で折り返すようなUILabelにしています。
また、UITableViewCellはself.addSubview
ではなく、self.contentView.addSubview
なので地味に注意。
このクラスがcellForRowAtで返されるcellインスタンスとなる。
TableViewの配置
import UIKit class ViewController: UIViewController { let contents = [ "short","midlemidlemidlemidlemidlemidlemidlemidle","longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong"] override func viewDidLoad() { super.viewDidLoad() let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) tableView.register( CustomListCell.self, forCellReuseIdentifier: "productCell" ) tableView.delegate = self tableView.dataSource = self self.view.addSubview(tableView) } }
tableView.register
で配置済みcellを再利用する際のdequeue、identifierを設定できる。
UITableViewDelegate、UITableViewDataSourceの実装
extension ViewController: UITableViewDelegate, UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 3 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "productCell", for: indexPath) as! CustomListCell cell.nameLabel.text = contents[indexPath.row] cell.nameLabel.sizeToFit() return cell } }
で、ここまででカスタムCellのnameLabelにindexPathに応じたテキストがwidth:300で折り返し表示される。
但し、当然、セルの高さは調整されないので、セルに収まらず、Labelが突き抜けて表示される。
高さを返すメソッドを実装して可変にする必要がある。
セルの高さの自動調整
以前にも書いた通りAutoLayoutを使わないのであれば、heightForRowAt
を実装してcellの高さを返す必要がある。
単純にカスタムcellのラベルの高さ + margin分を返せば良いのだけど、heightForRowAt
はcellForRowAt
より先に実行されるようで、
- この時点ではdequeueReusableCellでカスタムセルインスタンスを取得できない
let cell = tableView.cellForRow(at: indexPath) as! CustomListCell
ではCustomListCellにダウンキャストできない- 都度、CustomListCellのインスタンスを作成してLabelの高さを調べるとdequeueでcellを再利用する恩恵が薄れてしまう(メソッド内で破棄されるインスタンスなのでオーバーヘッドは少ないかも)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = CustomListCell() cell.nameLabel.text = contents[indexPath.row] cell.nameLabel.sizeToFit() return cell.nameLabel.frame.height }
などの問題があった。
で、自分が取った策は、CustomListCellクラス内に高さを図る為のモック的なUILabelを別にstaticで持てばいいじゃん。という方法。
前提としてモック的なUILabelは、cellに配置されるUILabelと同じRect定義でならなければならない。
import UIKit class CustomListCell: UITableViewCell{ static let nameLabelFrame = CGRect(x: 10, y: 10, width: 300, height: 0) let nameLabel = UILabel(frame: CustomListCell.nameLabelFrame) //高さ計算に使用するモックラベル static let mocLabel = UILabel(frame: nameLabelFrame) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) nameLabel.numberOfLines = 0 nameLabel.lineBreakMode = .byWordWrapping self.contentView.addSubview(nameLabel) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } //渡された文字列からLabelの高さを返す static func getLabelHeight(_ value:String)->CGFloat{ mocLabel.frame = CustomListCell.nameLabelFrame mocLabel.numberOfLines = 0 mocLabel.lineBreakMode = .byWordWrapping mocLabel.text = value mocLabel.sizeToFit() //maxYを返すのはposition.y分のマージンを含む為 return mocLabel.frame.maxY } }
これでcellインスタンスの生成なしでLabelの高さを返すメソッドが出来た。
あとは、heightForRowAtを実装すれば良い。
extension ViewController: UITableViewDelegate, UITableViewDataSource{ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return CustomListCell.getLabelHeight(contents[indexPath.row]) } ...略 }