UITableView with variable cell height: Working in IB but not programmatically

Suragch

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:

enter image description here

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:

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.

enter image description here

In IB

I set constraints to pin the top and bottom as well as centering the label.

enter image description here

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.

enter image description here

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:

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
Sulthan

The error is pretty trivial:

Instead of

self.addSubview(myCellLabel)

use

self.contentView.addSubview(myCellLabel)

Also, I would replace

// pin top
NSLayoutConstraint(...).active = true
// pin bottom
NSLayoutConstraint(...).active = true
// center horizontal
NSLayoutConstraint(...).active = true

with

let topConstraint = NSLayoutConstraint(...)
let bottomConstraint = NSLayoutConstraint(...)
let centerConstraint = NSLayoutConstraint(...)

self.contentView.addConstraints([topConstraint, bottomConstraint, centerConstraint])

which is more explicit (you have to specify the constraint owner) and thus safer.

The problem is that when calling active = true on a constraint, the layout system has to decide to which view it should add the constraints. In your case, because the first common ancestor of contentView and myCellLabel is your UITableViewCell, they were added to your UITableViewCell, so they were not actually constraining the contentView (constraints were between siblings not between superview-subview).

Your code actually triggered a console warning:

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.

Which made me to look immediately at the way the constraints are created for your label.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How to programmatically increase UITableView cell's height in iPhone?

From Dev

Resizing UITableview height programmatically

From Dev

Get UITableView cell height

From Dev

Uitableview cell dynamic height

From Dev

How to create a very basic UITableView programmatically with dynamic cell height and auto layout?

From Dev

How to select a uitableview cell programmatically?

From Dev

iOS 7/8 UITableView Cell: Two UILabels with dynamic height with auto layout for variable row height

From Dev

Dynamically change cell height programmatically

From Dev

Cell height not working properly

From Dev

Add UISearchBar to UITableView programmatically not working

From Dev

ContentView height is equal to cell height when creating custom cell programmatically?

From Dev

Swift UItableView Custom cell programmatically (documentation)?

From Dev

How to select UITableView cell programmatically (Swift 2)

From Dev

UITableView scroll to not-existsing cell programmatically

From Dev

Wrong cell insets in a UITableView added programmatically

From Dev

Simple UIStackView in UITableViewCell not working out height properly in IB

From Dev

UITableView inside UITableViewCell with dynamic cell height

From Dev

Set row height of a UITableView according to the cell in that row

From Dev

How to make fixed height cell on uitableview?

From Dev

Set height of a specific cell in my UITableView

From Dev

Set height of UITableView which contain dynamic Cell Height

From Dev

How to change cell height dynamically in UITableView static cell

From Dev

iOS 8 : how to set height programmatically for a UITableview in a freeform ViewController

From Dev

Dynamic UITableView Cell Heights Programmatically IOS7 in Swift

From Dev

How to create n number of UIButton programmatically in UITableView Cell?

From Dev

Dynamic UITableView cell height with AutoLayout with Dynamic Type Labels

From Dev

UITableView: How to change cell height dynamically when a button is clicked in it?

From Dev

UITableView dynamic cell heights - reload height for single row

From Dev

Static UITableView, make a single cell with dynamic height in Swift

Related Related

  1. 1

    How to programmatically increase UITableView cell's height in iPhone?

  2. 2

    Resizing UITableview height programmatically

  3. 3

    Get UITableView cell height

  4. 4

    Uitableview cell dynamic height

  5. 5

    How to create a very basic UITableView programmatically with dynamic cell height and auto layout?

  6. 6

    How to select a uitableview cell programmatically?

  7. 7

    iOS 7/8 UITableView Cell: Two UILabels with dynamic height with auto layout for variable row height

  8. 8

    Dynamically change cell height programmatically

  9. 9

    Cell height not working properly

  10. 10

    Add UISearchBar to UITableView programmatically not working

  11. 11

    ContentView height is equal to cell height when creating custom cell programmatically?

  12. 12

    Swift UItableView Custom cell programmatically (documentation)?

  13. 13

    How to select UITableView cell programmatically (Swift 2)

  14. 14

    UITableView scroll to not-existsing cell programmatically

  15. 15

    Wrong cell insets in a UITableView added programmatically

  16. 16

    Simple UIStackView in UITableViewCell not working out height properly in IB

  17. 17

    UITableView inside UITableViewCell with dynamic cell height

  18. 18

    Set row height of a UITableView according to the cell in that row

  19. 19

    How to make fixed height cell on uitableview?

  20. 20

    Set height of a specific cell in my UITableView

  21. 21

    Set height of UITableView which contain dynamic Cell Height

  22. 22

    How to change cell height dynamically in UITableView static cell

  23. 23

    iOS 8 : how to set height programmatically for a UITableview in a freeform ViewController

  24. 24

    Dynamic UITableView Cell Heights Programmatically IOS7 in Swift

  25. 25

    How to create n number of UIButton programmatically in UITableView Cell?

  26. 26

    Dynamic UITableView cell height with AutoLayout with Dynamic Type Labels

  27. 27

    UITableView: How to change cell height dynamically when a button is clicked in it?

  28. 28

    UITableView dynamic cell heights - reload height for single row

  29. 29

    Static UITableView, make a single cell with dynamic height in Swift

HotTag

Archive