TL;DR
My programmatically created table view cells are not resizing according to the intrinsic content height of their custom views, even though I am using UITableViewAutomaticDimension and setting both the top and bottom constraints. 
The problem probably lies in my implementation of the UITableViewCell subclass. See the code below under Doesn't work programmatically > Code > MyCustomCell.swift. 
Goal
I'm trying to make a suggestion bar for a custom Mongolian keyboard. Mongolian is written vertically. In Android it looks like this:
Progress
I've learned that I should use a UITableView with variable cell heights, which is available starting with iOS 8. This requires using auto layout and telling the table view to use automatic dimensions for the cell heights.
Some things I've had to learn along the way are represented in my recent SO questions and answers:
- How to make a custom table view cell
- Getting variable height to work with in a table view with a standard UILabel
- Getting intrinsic content size to work for a custom view
- Using a programmatically created UITableViewCell
- Set constraints programmatically
So I have come to the point where I have the vertical labels that support intrinsic content size. These labels go in my custom table view cells. And as described in the next section, they work when I do it in the storyboard, but not when I create everything programmatically.
Works in IB
In order to isolate the problem I created two basic projects: one for where I use the storyboard and one where I do everything programmatically. The storyboard project works. As can be seen in the following image, each table view cell resizes to match the height of custom vertical label.
In IB
I set constraints to pin the top and bottom as well as centering the label.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
    let cellReuseIdentifier = "cell"
    @IBOutlet var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }
    // number of rows in table view
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.myStrings.count
    }
    // create a cell for each table view row
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.myStrings[indexPath.row]
        return cell
    }
    // method to run when table view cell is tapped
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}
MyCustomCell.swift
import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UIMongolSingleLineLabel!
}
Doesn't work programmatically
Since I want the suggestion bar to be a part of the final keyboard, I need to be able to create it programmatically. However, when I try to recreate the above example project programmatically, it isn't working. I get the following result.
The cell heights are not resizing and the custom vertical labels are overlapping each other.
I also get the following error:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
This error has been brought up before multiple times on Stack Overflow:
- iOS8 - constraints ambiguously suggest a height of zero
- Detected a case where constraints ambiguously suggest a height of zero
- custom UITableviewcell height not set correctly
- ios 8 (UITableViewCell) : Constraints ambiguously suggest a height of zero for a tableview cell's content view
However, the problem for most of those people is that they were not setting both a top and bottom pin constraint. I am, or at least I think I am, as is shown in my code below.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
    let cellReuseIdentifier = "cell"
    var tableView = UITableView()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Suggestion bar
        tableView.frame = CGRect(x: 0, y: 20, width: view.bounds.width, height: view.bounds.height)
        tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
        view.addSubview(tableView)
    }
    // number of rows in table view
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.myStrings.count
    }
    // create a cell for each table view row
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.myStrings[indexPath.row]
        return cell
    }
    // method to run when table view cell is tapped
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}
MyCustomCell.swift
I think the problem is probably in here since this is the main difference from the IB project.
import UIKit
class MyCustomCell: UITableViewCell {
    var myCellLabel = UIMongolSingleLineLabel()
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.setup()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    func setup() {
        self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false
        self.myCellLabel.centerText = false
        self.myCellLabel.backgroundColor = UIColor.yellowColor()
        self.addSubview(myCellLabel)
        // Constraints
        // pin top
        NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
        // pin bottom
        NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
        // center horizontal
        NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
    }
    override internal class func requiresConstraintBasedLayout() -> Bool {
        return true
    }
}
Supplemental Code
I'll also include the code for the custom vertical label that I used in both projects above, but since the IB project works, I don't think the main problem is here.
import UIKit
@IBDesignable
class UIMongolSingleLineLabel: UIView {
    private let textLayer = LabelTextLayer()
    var useMirroredFont = false
    // MARK: Primary input value
    @IBInspectable var text: String = "A" {
        didSet {
            textLayer.displayString = text
            updateTextLayerFrame()
        }
    }
    @IBInspectable var fontSize: CGFloat = 17 {
        didSet {
            updateTextLayerFrame()
        }
    }
    @IBInspectable var centerText: Bool = true {
        didSet {
            updateTextLayerFrame()
        }
    }
    // MARK: - Initialization
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    func setup() {
        // Text layer
        textLayer.backgroundColor = UIColor.yellowColor().CGColor
        textLayer.useMirroredFont = useMirroredFont
        textLayer.contentsScale = UIScreen.mainScreen().scale
        layer.addSublayer(textLayer)
    }
    override func intrinsicContentSize() -> CGSize {
        return textLayer.frame.size
    }
    func updateTextLayerFrame() {
        let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ]
        let attrString = NSMutableAttributedString(string: textLayer.displayString, attributes: myAttribute )
        let size = dimensionsForAttributedString(attrString)
        // This is the frame for the soon-to-be rotated layer
        var x: CGFloat = 0
        var y: CGFloat = 0
        if layer.bounds.width > size.height {
            x = (layer.bounds.width - size.height) / 2
        }
        if centerText {
            y = (layer.bounds.height - size.width) / 2
        }
        textLayer.frame = CGRect(x: x, y: y, width: size.height, height: size.width)
        textLayer.string = attrString
        invalidateIntrinsicContentSize()
    }
    func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize {
        var ascent: CGFloat = 0
        var descent: CGFloat = 0
        var width: CGFloat = 0
        let line: CTLineRef = CTLineCreateWithAttributedString(attrString)
        width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil))
        // make width an even integer for better graphics rendering
        width = ceil(width)
        if Int(width)%2 == 1 {
            width += 1.0
        }
        return CGSize(width: width, height: ceil(ascent+descent))
    }
}
// MARK: - Key Text Layer Class
class LabelTextLayer: CATextLayer {
    // set this to false if not using a mirrored font
    var useMirroredFont = true
    var displayString = ""
    override func drawInContext(ctx: CGContext) {
        // A frame is passed in, in which the frame size is already rotated at the center but the content is not.
        CGContextSaveGState(ctx)
        if useMirroredFont {
            CGContextRotateCTM(ctx, CGFloat(M_PI_2))
            CGContextScaleCTM(ctx, 1.0, -1.0)
        } else {
            CGContextRotateCTM(ctx, CGFloat(M_PI_2))
            CGContextTranslateCTM(ctx, 0, -self.bounds.width)
        }
        super.drawInContext(ctx)
        CGContextRestoreGState(ctx)
    }
}
Update
The entire code for the project is all here, so if anyone is interested enough to try it out, just make a new project and cut and paste the code above into the following three files:
- ViewController.swift
- MyCustomCell.swift
- UIMongolSingleLineLabel.swift




 
     
     
    
 
     
     
    