Trip Trip - 6 months ago 53
Swift Question

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?

I've spent several days trying to figure this out, but there doesn't seem to be a solution. I have a very basic UITableView cell with two labels in it. One of them will be one line, the second will be multiline. The second one will be varying in heights. Here is an example of what I'm trying to produce ( UIStackViews inside of UITableViewCells ) :

enter image description here

From reading hundreds of SO posts, hundreds of blogs, scouring the Apple Developer sites, there seems to be an unlimited amount of ways one could write this. After having experimented with hundreds of different ways, I still can't manage to display the text like so. I think some of the trouble comes from the fact that I'm using 3 different UITableView cell variations in one UITableView.

Here is my code from my latest attempt

cellForRowAtIndexPath
:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

var cell : UITableViewCell!

let (parent, isParentCell) = self.findParent(indexPath.row)

if !isParentCell {

let categories = ["words", "translation","commentary"]

if categories[parent] == "words" {

cell = tableView.dequeueReusableCellWithIdentifier(childWordsCellIdentifier, forIndexPath: indexPath) as UITableViewCell

// Reset content
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
cell.prepareForReuse()


let words = self.page.valueForKey("words")!.allObjects as! [Word]
let wordsInOrder = words.sort({Int($0.order!) < Int($1.order!) })
let word = wordsInOrder[indexPath.row - 1]

let labelWidth = (self.view.frame.width - 80) / 2


let wordLabel = UILabel(frame: CGRectZero)
wordLabel.text = word.valueForKey("sanskrit") as? String
wordLabel.font = UIFont(name: "EuphemiaUCAS-Bold", size: 16)
wordLabel.numberOfLines = 0
wordLabel.translatesAutoresizingMaskIntoConstraints = false
wordLabel.layer.borderColor = UIColor.greenColor().CGColor
wordLabel.layer.borderWidth = 1.0
let widthWordConstraint = wordLabel.widthAnchor.constraintEqualToConstant(labelWidth)
widthWordConstraint.priority = 300
widthWordConstraint.active = true

let englishWordLabel = UILabel(frame: CGRectZero)
englishWordLabel.text = "Foobar don't want to play my game with my anymore."
englishWordLabel.font = UIFont(name: "STHeitiTC-Light", size: 16)
englishWordLabel.numberOfLines = 0
englishWordLabel.preferredMaxLayoutWidth = labelWidth
englishWordLabel.translatesAutoresizingMaskIntoConstraints = false
let englishWordConstraint = englishWordLabel.widthAnchor.constraintEqualToConstant(labelWidth)
englishWordConstraint.priority = 300
englishWordConstraint.active = true
englishWordLabel.layer.borderColor = UIColor.blueColor().CGColor
englishWordLabel.layer.borderWidth = 1.0

let stackView = UIStackView()
stackView.axis = .Horizontal
stackView.distribution = .FillProportionally
stackView.alignment = .FirstBaseline
stackView.spacing = 15
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.layoutMarginsRelativeArrangement = true

stackView.addArrangedSubview(wordLabel)
stackView.addArrangedSubview(englishWordLabel)

stackView.translatesAutoresizingMaskIntoConstraints = false

englishWordLabel.topAnchor.constraintEqualToAnchor(stackView.topAnchor).active = true
englishWordLabel.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor).active = true


cell.contentView.addSubview(stackView)

cell.contentView.layoutIfNeeded()


} else {

cell = tableView.dequeueReusableCellWithIdentifier(childCellIdentifier, forIndexPath: indexPath) as UITableViewCell

// Reset content
cell.textLabel!.text = ""
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
cell.prepareForReuse()

cell.textLabel!.text = self.page.valueForKey(categories[parent]) as? String
cell.textLabel!.textColor = UIColor(red: 35/255.0, green: 31/255.0, blue: 32/255.0, alpha: 1.0)
cell.textLabel!.font = UIFont(name: "STHeitiTC-Light", size: 16)
}
}
else {
// Parent
cell = tableView.dequeueReusableCellWithIdentifier(parentCellIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = self.dataSource[parent].title
cell.textLabel!.textColor = UIColor(red: 66/255.0, green: 116/255.0, blue: 185/255.0, alpha: 1.0)
cell.textLabel!.font = UIFont(name: "STHeitiTC-Light", size: 20)
}

cell.selectionStyle = .None
cell.textLabel!.translatesAutoresizingMaskIntoConstraints = false
cell.textLabel!.numberOfLines = 0
cell.textLabel!.lineBreakMode = .ByWordWrapping

return cell
}


Which produces this :

enter image description here

Answer

The hight of a table view cell is decided by the table based on the rowHeight property, or on the return value of optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat from the table view delegate.

The hight that may be set by the programmer for the UITableViewCell through it's frame property is ignored!

Hence you need to figure out the desired hight of each cell programatically. Then you need to implement optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat from the table view delegate for each of the cells.

If the size of a cell changes while the table view is displayed, you need to either:

  • reload the table with reloadData() from UITableView, or
  • update the cell with func reloadRowsAtIndexPaths(_ indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation) from UITableView.

Edit

Actually since you are using constraints in you view it should work without using ``.

Try to replace the code in if categories[parent] == "words" { .. } with:

        cell = tableView.dequeueReusableCellWithIdentifier(childWordsCellIdentifier, forIndexPath: indexPath) as UITableViewCell

        // Reset content
        for subview in cell.contentView.subviews {
            subview.removeFromSuperview()
        }
        cell.prepareForReuse()


        let words                   = self.page.valueForKey("words")!.allObjects as! [Word]
        let wordsInOrder            = words.sort({Int($0.order!) < Int($1.order!) })
        let word                    = wordsInOrder[indexPath.row - 1]


        let wordLabel               = UILabel(frame: CGRectZero)
        wordLabel.text              = word.valueForKey("sanskrit") as? String
        wordLabel.font              = UIFont(name: "EuphemiaUCAS-Bold", size: 16)
        wordLabel.numberOfLines     = 0
        wordLabel.translatesAutoresizingMaskIntoConstraints = false
        wordLabel.layer.borderColor = UIColor.greenColor().CGColor
        wordLabel.layer.borderWidth = 1.0

        let englishWordLabel           = UILabel(frame: CGRectZero)
        englishWordLabel.text          = "Foobar don't want to play my game with my anymore."
        englishWordLabel.font          = UIFont(name: "STHeitiTC-Light", size: 16)
        englishWordLabel.numberOfLines = 0

        let stackView = UIStackView()
        stackView.axis = .Horizontal
        stackView.distribution = .FillProportionally
        stackView.alignment = .Fill  // EDIT
        stackView.spacing = 15
        stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
        stackView.layoutMarginsRelativeArrangement = true

        stackView.addArrangedSubview(wordLabel)
        stackView.addArrangedSubview(englishWordLabel)

        stackView.translatesAutoresizingMaskIntoConstraints = false

        cell.contentView.addSubview(stackView)
        cell.contentView.leadingAnchor.constraintEqualToAnchor(stackView.leadingAnchor).active = true
        cell.contentView.topAnchor.constraintEqualToAnchor(stackView.topAnchor).active = true
        cell.contentView.trailingAnchor.constraintEqualToAnchor(stackView.trailingAnchor).active = true
        cell.contentView.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor).active = true

        cell.contentView.layoutIfNeeded()

Also I would have used to different cells classes for this task rather than removing the views in the contentView.

Please let me know how it goes!

Comments