How to Have Differing Heights in a Uitableview Cell When I Use Several Different Ways of Displaying the Cell

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

The height 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 height that may be set by the programmer for the UITableViewCell through its frame property is ignored!

Hence you need to figure out the desired height 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 two different cells classes for this task rather than removing the views in the contentView.

Please let me know how it goes!

Different UITableViewCell heights

of course it crashed, because you are in a delegate method, (you are a delegate of the UITableView) and from such method calling back the UITableView methods takes very high risk of crash.

the method ([tableView cellForRowAtIndexPath:indexPath]), what you call, will call a delegate method again for the cell's height, and it causes an infinite loop, aka crash.

this is normal behaviour.

the key is in your original data source, you could store the height for each row in the original data source, and you can read everything back from there, in your delegate methods without any risk.

your code fragment does not say where your data source is and what kind of your data source is, thus I cannot give more exact solution in lack of it, but the idea would be that.

How to use dynamic height of UITableViewCell in auto-layout and move other views to up when bottom view is hidden?

At first, make the height of UITableViewCell to UITableView.automaticDimension

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}

Embed all of your questionLabels in a UIStackView (vertical) excluding bottomLabel. Set AutoLayoutConstraint between UIStackView and bottomLabel.

Sample Image

Set the numberOfLines property of UILabels to 0(zero).

Sample Image

Set the Distribution of the UIStackView as Fill

Sample Image

Then, in your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method hide the labels. And it will automatically handle the spaces between UILabels

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell

cell.questionLabel1.text = labelOneText[indexPath.row]
cell.questionLabel2.text = labelTwoText[indexPath.row]
cell.questionLabel3.text = labelThreeText[indexPath.row]

if labelOneText[indexPath.row] == "" {
cell.questionLabel1.isHidden = true
}

if labelTwoText[indexPath.row] == "" {
cell.questionLabel2.isHidden = true
}

if labelThreeText[indexPath.row] == "" {
cell.questionLabel3.isHidden = true
}

return cell
}

Final Output:

Sample Image

How do I have two separate cells with two different heights?

You can put your first cell in a different section.

func numberOfSections(in tableView: UITableView) -> Int {
return 2
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}else {
return yourArray.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! FirstCustomCell
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! SecondCustomCell
return cell
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 1 {
return 100
} else{
return 40
}

How do I change UITableViewCell height?

Here is an example (Swift 3) of using auto-layout and auto-sizing cells. Not sure how you plan to "start" with the green view, so this demo initializes it to 40-pts wide (height is contained to 50% of the width).

You can simply create a new .swift file and paste this in, then add a UITableViewController in Storyboard and assign its class to StretchCellTableViewController. Everything else is created in code... no IBOutlets needed.

//
// StretchCellTableViewController.swift
// SWTemp2
//
// Created by Don Mag on 6/22/17.
// Copyright © 2017 DonMag. All rights reserved.
//

import UIKit

class NonStretchCell: UITableViewCell {

// pretty standard one-label cell

var theLabel: UILabel!

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupCell()
}

func setupCell() {

theLabel = UILabel()
theLabel.font = UIFont.systemFont(ofSize: 16.0, weight: UIFontWeightLight)
theLabel.numberOfLines = 0
theLabel.translatesAutoresizingMaskIntoConstraints = false

self.addSubview(theLabel)

theLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8.0).isActive = true
theLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 6.0).isActive = true
theLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -6.0).isActive = true
theLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8.0).isActive = true

}

}

class StretchCell: UITableViewCell {

var stretchView: UIView!

var widthConstraint: NSLayoutConstraint!

var scaleAction : ((CGFloat)->())?

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupCell()
}

func setBarWidth(_ w: CGFloat) -> Void {
widthConstraint.constant = w
}

func setupCell() {

// instantiate the view
stretchView = UIView()
stretchView.translatesAutoresizingMaskIntoConstraints = false
stretchView.backgroundColor = .green

// add it to self (the cell)
self.addSubview(stretchView)

// "pin" it to top, bottom and left
stretchView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0).isActive = true
stretchView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0).isActive = true
stretchView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0).isActive = true

// set height constraint to be 50% of the width
stretchView.heightAnchor.constraint(equalTo: stretchView.widthAnchor, multiplier: 0.5).isActive = true

// instantiate the width constraint
widthConstraint = NSLayoutConstraint(item: stretchView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)

// needs priority of 999 so auto-layout doesn't complain
widthConstraint.priority = 999

// activate the width constraint
NSLayoutConstraint.activate([widthConstraint])

// create a UIPanGestureRecognizer and add it to the stretch view
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))

stretchView.addGestureRecognizer(panGesture)

}

func didPan(sender: UIPanGestureRecognizer) {

let loc = sender.location(in: self)

if sender.state == .began {

// if you want to do something on drag start...
//print("Gesture began")

} else if sender.state == .changed {

//print("Gesture is changing", loc)

// update the width of the stretch view (but keep it at least 20-pts wide, so it doesn't disappear)
// height is set to 1:1 so it updates automatically
widthConstraint.constant = max(loc.x, 20)

// call back to the view controller
scaleAction?(widthConstraint.constant)

} else if sender.state == .ended {

// if you want to do something on drag end...
//print("Gesture ended")

}

}

}

class StretchCellTableViewController: UITableViewController {

// array to track the bar widths, assuming we have more than one row with a bar
// only a few rows will have the "stretch bar" - but for this example we'll just track a barWidth for *every* row
var barWidths = [CGFloat]()

override func viewDidLoad() {
super.viewDidLoad()

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80

tableView.register(StretchCell.self, forCellReuseIdentifier: "StretchCell")
tableView.register(NonStretchCell.self, forCellReuseIdentifier: "NonStretchCell")

// init array for barWidths... initial value: 40, 30 slots
barWidths = Array(repeating: 40, count: 30)
}

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return barWidths.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

// just make every 5th row a "Stretch" cell (starting at #3)
if indexPath.row % 5 == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "StretchCell", for: indexPath) as! StretchCell
cell.selectionStyle = .none

// cells are reused, so set the bar width to our saved value
cell.setBarWidth(barWidths[indexPath.row])

// "call-back" function
cell.scaleAction = {
(newWidth) in
// update the bar width in our tracking array
self.barWidths[indexPath.row] = newWidth

// disable Animations, because we're changing the size repeatedly
UIView.setAnimationsEnabled(false)

// wrap begin/end updates() to force row-height re-calc
self.tableView.beginUpdates()
self.tableView.endUpdates()

// re-enable Animations
UIView.setAnimationsEnabled(true)
}

cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero

return cell
}

let cell = tableView.dequeueReusableCell(withIdentifier: "NonStretchCell", for: indexPath) as! NonStretchCell

// just for demonstration's sake

if indexPath.row % 7 == 5 {
cell.theLabel.text = "Row: \(indexPath.row) - This is just to show the text wrapping onto multiple lines. Pretty standard for auto-sizing UITableView cells."
} else {
cell.theLabel.text = "Row: \(indexPath.row)"
}

cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero

return cell
}

}

Result is:

Sample Image

getting the height of the custom cell in UITableViewCell

You can use Autolayout for the same and get free from all the coding part. Just set the constraint of the inner view in association with the cell and then right the following code.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension

}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell:ChatSenderTableViewCell = (tableView.dequeueReusableCell(withIdentifier: "ChatSenderTableViewCell", for: indexPath) as? ChatSenderTableViewCell)!

cell.LabelChatMessage.text = "Your Message"
cell.LabelChatMessage.numberOfLines = 0
cell.LabelChatMessage.sizeToFit()

return cell

}

I am using a custom cell name ChatSenderTableViewCell to create a bubble like effect containing a label for messages send or receive.



Related Topics



Leave a reply



Submit