Programmatically Creating an Expanding Uitableviewcell

Programmatically creating an expanding UItableViewCell

Use vertical UIStackView with the bottom view isHidden set to true, then on tap (or whatever is the trigger of the expand) just change the isHidden = false. I guess that would be the easiest, considering how UIStackView deals with isHidden. Another approach is to setup autolayout constraints, and change height anchor of the bottom view by setting NSLayoutConstraint's constant to 0.

Anyway, whichever of the appraoch will you choose, you will have to tell the tableView to refresh its display (from the viewcontroller):

func refreshTableAfterCellExpansion() {
self.tableView.beginUpdates()
self.tableView.setNeedsDisplay()
self.tableView.endUpdates()
}

E.g., check following SO question and its answer.

An example using playgrounds (the one with the UIStackView, the other one uses the same principle):

import UIKit
import PlaygroundSupport

class ExpandableCellViewController: UITableViewController, ExpandableCellDelegate {

override func loadView() {
super.loadView()

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
tableView.register(ExpandableCell.self, forCellReuseIdentifier: "expandableCell")
}

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "expandableCell", for: indexPath) as! ExpandableCell
cell.delegate = self
return cell
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell {
cell.isExpanded = true
}
}

override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell {
cell.isExpanded = false
}
}

func expandableCellLayoutChanged(_ expandableCell: ExpandableCell) {
refreshTableAfterCellExpansion()
}

func refreshTableAfterCellExpansion() {
self.tableView.beginUpdates()
self.tableView.setNeedsDisplay()
self.tableView.endUpdates()
}
}

protocol ExpandableCellDelegate: class {
func expandableCellLayoutChanged(_ expandableCell: ExpandableCell)
}

class ExpandableCell: UITableViewCell {
weak var delegate: ExpandableCellDelegate?

fileprivate let stack = UIStackView()
fileprivate let topView = UIView()
fileprivate let bottomView = UIView()

var isExpanded: Bool = false {
didSet {
bottomView.isHidden = !isExpanded
delegate?.expandableCellLayoutChanged(self)
}
}

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

selectionStyle = .none

contentView.addSubview(stack)
stack.addArrangedSubview(topView)
stack.addArrangedSubview(bottomView)

stack.translatesAutoresizingMaskIntoConstraints = false
topView.translatesAutoresizingMaskIntoConstraints = false
bottomView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: contentView.topAnchor),
stack.leftAnchor.constraint(equalTo: contentView.leftAnchor),
stack.rightAnchor.constraint(equalTo: contentView.rightAnchor),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),

topView.heightAnchor.constraint(equalToConstant: 50),

bottomView.heightAnchor.constraint(equalToConstant: 30),
])

stack.axis = .vertical
stack.distribution = .fill
stack.alignment = .fill
stack.spacing = 0

topView.backgroundColor = .red
bottomView.backgroundColor = .blue
bottomView.isHidden = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ExpandableCellViewController()

How to expand and collapse tableview cells with dynamic height programmatically?

You can use UIStackView for expand and collapse tableview. You can hide and show the description label when tableview cell is selected.

class ViewController: UIViewController {
var tableView: UITableView = {
let tv = UITableView(frame: .zero)
tv.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tv.translatesAutoresizingMaskIntoConstraints = false
tv.rowHeight = UITableView.automaticDimension
tv.estimatedRowHeight = 100.0
tv.estimatedSectionHeaderHeight = 0
tv.estimatedSectionFooterHeight = 0
tv.showsVerticalScrollIndicator = false
tv.tableFooterView = UIView()
tv.alwaysBounceVertical = true
tv.decelerationRate = .fast
tv.bounces = false
return tv
}()
var selectedCell:IndexPath?

override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: ["tableView":tableView]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: ["tableView":tableView]))
}


}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell ?? CustomCell()
if let selectedCell = selectedCell, selectedCell == indexPath {
cell.descriptionLabel.isHidden = false
} else {
cell.descriptionLabel.isHidden = true
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath
tableView.reloadData()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}

}

class CustomCell: UITableViewCell {

let stackView = UIStackView()
let wordLabel = UILabel()
let descriptionLabel = UILabel()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLabels()
}

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

func setupLabels() {

selectionStyle = .none

stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.alignment = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(stackView)

wordLabel.translatesAutoresizingMaskIntoConstraints = false
wordLabel.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lor"
wordLabel.numberOfLines = 0
wordLabel.lineBreakMode = .byWordWrapping
stackView.addArrangedSubview(wordLabel)

descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.text = "It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
descriptionLabel.numberOfLines = 0
descriptionLabel.lineBreakMode = .byWordWrapping
stackView.addArrangedSubview(descriptionLabel)

wordLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true
descriptionLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true

stackView.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor,constant: 10).isActive = true
stackView.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor,constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor,constant: 10).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor,constant: 10).isActive = true

}
}

Expand UITableView cell in UITableViewstylePlain

Here is the complete Tutorial with Expandable UITableView

Here’s the code snip for that.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// only first row toggles exapand/collapse
[tableView deselectRowAtIndexPath:indexPath animated:YES];

NSInteger section = indexPath.section;
BOOL currentlyExpanded = [expandedSections containsIndex:section];
NSInteger rows;

NSMutableArray *tmpArray = [NSMutableArray array];

if (currentlyExpanded)
{
rows = [self tableView:tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];

}
else
{
[expandedSections addIndex:section];
rows = [self tableView:tableView numberOfRowsInSection:section];
}

for (int i=1; i {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i
inSection:section];
[tmpArray addObject:tmpIndexPath];
}

UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

if (currentlyExpanded)
{
[tableView deleteRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];

cell.accessoryView = [DTCustomColoredAccessory accessoryWithColor:[UIColor grayColor] type:DTCustomColoredAccessoryTypeDown];

}
else
{
[tableView insertRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
cell.accessoryView = [DTCustomColoredAccessory accessoryWithColor:[UIColor grayColor] type:DTCustomColoredAccessoryTypeUp];

}
}
}
}

as shown below picture

Sample Image

This and this one solution also help you to understand how to manage Expandable TableView

Expand and Collapse tableview cells

If you want the cell to get physically bigger, then where you have your store IndexPath, in heightForRow: use:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return 230 + extraHeight
}
return 230.0
}

Then when you want to expand one in the didSelectRow:

selectedIndexPath = indexPath
tableView.beginUpdates
tableView.endUpdates

Edit

This will make the cells animate themselves getting bigger, you dont need the extra animation blocks in the cell.

Edit 2

 override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil

if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.collapse()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.collapse()
}
} else {
selectedIndexPath = indexPath

if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.expand()
}

if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.expand()
}
}

tableView.beginUpdates()
tableView.endUpdates()
}

Is there a way to fit long texts inside the tableview cell which is expandable cell? (programmatically)

Add right and bottom constraints to cevapLabel. width & height be nil.

Set numberOfLines to 0.

Use UITableViewAutomaticDimension.

Now cevapLabel will set it's size according to the text.

let cevapLabel: UILabel = {
let cevaplabel = UILabel()
cevaplabel.textColor = .black
cevaplabel.font = UIFont.boldSystemFont(ofSize: 18)
cevaplabel. numberOfLines = 0
return cevaplabel
}()



func setup() {
...
...

cevapLabel.anchor(top: soruLabel.bottomAnchor, left: cellView.leftAnchor, bottom: bottom: cellView.bottomAnchor, right: cellView.rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: nil, height: nil)
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 80 // Give estimated Height Fo rRow here
}

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

Wrap UILabel by word and expand UITableViewCell height programmatically

  1. Line wrapping by word

Adding this additional constraint (set width of label to parent view) did enable word wrapping:

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[attributeNameLabel(==attributeNameView)]|"
options: 0
metrics:nil
views:views];

[self.contentView addConstraints:constraints];

Finally, I had to center the text:

[self.attributeNameLabel setTextAlignment:NSTextAlignmentCenter];

  1. Expanding the cell height based on contents

It is also needed to set these properties for UITableView

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight =

Set the height of the wrapping superview tame as the label it contains

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameUIView(==nameUILabel)]|"
options: 0
metrics:nil
views:views];

[self.contentView addConstraints:constraints];

Minimum height constraint (optional)

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameUIView(>=50)]|"
options: 0
metrics:nil
views:views];

[self.contentView addConstraints:constraints];

UITableViewCell expand on click

Implement heightForRowAtIndexPath to calculate the right height. Then in the code for your button, force the table to reevaluate each cell's height with beginUpdates plus endUpdates:

[self.tableView beginUpdates];
[self.tableView endUpdates];

Changes to the tableview cells' heights will automatically be calculated with heightForRowAtIndexPath and the changes will be animated too.

In fact, instead of a button on your cell that does this, you might even just make selecting the cell do this in didSelectRowAtIndexPath.

Creating custom table View with Cell properties programmatically

In setUpUI() method of your RoomViewController class, set tableView's rowHeight to UITableView.automaticDimension and estimatedRowHeight to 100.

import UIKit

class RoomViewController: UIViewController {

private func setUpUI() {

view.backgroundColor = .white

title = "Room List "
view.addSubview(tableView)

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension

// create constraints
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true


// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

}
}

And add UIStackView in the RoomCellTableViewCell class as bellow:

import UIKit

class RoomCellTableViewCell: UITableViewCell {

private var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubview(stackView)

NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leftAnchor.constraint(equalTo: leftAnchor),
stackView.rightAnchor.constraint(equalTo: rightAnchor)
])

stackView.addArrangedSubview(createdAtLabel)
stackView.addArrangedSubview(isOccupiedLabel)
stackView.addArrangedSubview(maxOccupancyLabel)
// stackView.addArrangedSubview(idLabel)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}


Related Topics



Leave a reply



Submit