AutoLayoutでUITableViewCellに異なる高さを設定する

先週の勉強会#swiftwozに来てくださった方ありがとうございました。自分のパートは準備不足ですみませんでした。何人かの方にGithubswift-layoutを使っていただき、重ね重ねありがとうございます。


今日はUITableViewのcellでAuto Layoutを使ってみました。
このようなケースを想定しています。

  • UITableViewに各行で異なる高さのUITableViewCellを表示
  • CellのレイアウトはAuto Layoutで設定
  • CellはdequeueReusableCellWithIdentifierでキャッシュされたものを使う


UITableViewCellのcontentViewにAuto Layoutで書いたsubviewを入れても、なぜかcontentViewの高さが変更されないところではまりました。setNeedsLayou(),layoutIfNeede(),layoutSubviews()あたりを使っても高さが変わらなかったので、subviewのboundsから高さを直接計算してheightForRowAtIndexPathの戻り値にしているところがポイントです。

また、heightForRowAtIndexPathメソッド内で高さを計算する時に、tableView.dequeueReusableCellWithIdentifier("MyTableViewCell", forIndexPath: indexPath)を使用すると実行時に落ちるので、高さ計算用のCellインスタンス(tmpCalculatHeightCell)を一つ用意しています。


サンプルコードは、Auto Layoutをラップしたswift-layoutを使用していますが、直接NSLayoutConstraintを使用しても動くはずです。



コード

https://github.com/grachro/DEMO-Swift-AutoLayout-ResizableTableViewCell
全文

import UIKit

class ViewController: UIViewController {
    
    let tableView = UITableView()
    var tmpCalculatHeightCell:MyTableViewCell?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Auto Layout
        Layout.regist(tableView,superview: self.view)
            .coverSuperView()
        
        self.tableView.registerClass(MyTableViewCell.self, forCellReuseIdentifier: "MyTableViewCell")
        self.tableView.dataSource = self
        self.tableView.delegate = self
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    
}

extension ViewController:UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = self.tableView.dequeueReusableCellWithIdentifier("MyTableViewCell", forIndexPath: indexPath) as MyTableViewCell
        
        let containtHeight = CGFloat(indexPath.row + 1) * 5
        cell.setHeight(containtHeight)
        
        return cell
    }
    
}

extension ViewController:UITableViewDelegate {
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        
        let containtHeight = CGFloat(indexPath.row + 1) * 5
        
        if self.tmpCalculatHeightCell == nil {
            self.tmpCalculatHeightCell = self.tableView.dequeueReusableCellWithIdentifier("MyTableViewCell") as? MyTableViewCell
        }
        
        if let cell = self.tmpCalculatHeightCell as MyTableViewCell! {
            cell.setHeight(containtHeight)
            
            cell.setNeedsLayout()
            cell.layoutIfNeeded()
            
            return cell.totalCellHeight
        }
        
        return 0
    }
    
}

class MyTableViewCell : UITableViewCell {
    
    var widthConstraint: NSLayoutConstraint?
    var heightConstraint: NSLayoutConstraint?
    
    var baseViewLayout:Layout?
    var resizableViewLayout:Layout?
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        self.contentView.backgroundColor = UIColor.redColor()
        
        //Auto Layout
        self.baseViewLayout = Layout.registUIView(superview: self.contentView)
            .widthIsSame(self.contentView)
        
        //Auto Layout
        self.resizableViewLayout = Layout.registUIView(superview: self.baseViewLayout!.view)
            .top(10).fromSuperviewTop()
            .bottom(10).fromSuperviewBottom()
            .width(0).lastConstraint(&widthConstraint)
            .height(0).lastConstraint(&heightConstraint)
            .horizontalCenterInSuperview()
            .backgroundColor(UIColor.yellowColor())
        
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setHeight(height:CGFloat) {
        self.widthConstraint?.constant = height
        self.heightConstraint?.constant = height
    }
    
    var totalCellHeight:CGFloat {
        return baseViewLayout!.view.bounds.height
    }
}