How to change layout and property setting in tableview willDisplay function?

uitableview, willdisplaycell animation
tableview willdisplaycell swift 4
uitableview did display cell
uitableviewdatasource
uitableviewcell
dequeuereusablecellwithidentifier
incorrect cell data display when scrolling uitableview swift
uitableviewcell reuse problem swift

I have a question. I have a UITableView and a custom UITableViewCell. First, I'll check the UILabel which in custom UITableViewCell whether is truncated or not. If UILabel is trencated, I'll show the UIButton "show more", and set the UILabel numberOfLines equal to 2. When I click the UIButton "show more", I set the UILabel numberOfLines equal to 0, and the UIButton title change to "Close". The height of UITableViewCell is accroding to expanding UILabel content. If UILabel isn't trencated, I don't show the UIButton and set UILabel numberOfLines equal to 0. The height of UITableViewCell is also accroding to expanding UILabel content. How to acheive this situation? Thanks.

How to remove red square area in following pic?

class ViewController: UIViewController {

    let itemCount: Int = 10
    let tableView = UITableView()
    let cellWithButton = "cellWithButton"
    var isExpand: Bool = false
    var expandingStateArray: [Bool] = []

    let textArray: [String] = ["If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm", "If you read and listen to two", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day", "If you read and listen to two articles every day, your reading and listening skills", "If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm If you read and listen to two articles every day, your reading and listening skills can immm"]

    override func viewDidLoad() {
        super.viewDidLoad()

        for _ in 0...itemCount-1 {
            let bool = false
            expandingStateArray.append(bool)
        }

        tableView.delegate = self
        tableView.dataSource = self
        tableView.allowsSelection = false
        tableView.separatorInset = .zero
        tableView.estimatedRowHeight = 44
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.register(WithButtonTableViewCell.self, forCellReuseIdentifier: cellWithButton)

        self.view.addSubview(tableView)

        tableView.snp.makeConstraints { (make) in
            make.top.left.right.bottom.equalToSuperview()
        }
    }

    @objc func btnPressed(sender: UIButton) {

        let indexPath = IndexPath(row: sender.tag, section: 0)

        if self.isExpand == false {

            self.isExpand = true

            expandingStateArray[sender.tag] = true

        } else {
            self.isExpand = false

            expandingStateArray[sender.tag] = false
        }

        tableView.beginUpdates()
        tableView.reloadRows(at: [indexPath], with: .none)
        tableView.endUpdates()

    }


}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

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

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

        let cell = tableView.dequeueReusableCell(withIdentifier: cellWithButton, for: indexPath) as! WithButtonTableViewCell

        cell.titleLabel.text = textArray[indexPath.row]
        cell.expandButton.addTarget(self, action: #selector(btnPressed), for: .touchUpInside)
        cell.expandButton.tag = indexPath.row

        if expandingStateArray[indexPath.row] {
            cell.titleLabel.numberOfLines = 0
            cell.expandButton.setTitle("Close.", for: .normal)
        }else{
            cell.titleLabel.numberOfLines = 2
            cell.expandButton.setTitle("Show More.", for: .normal)
        }

        return cell
    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        if let btnCell = cell as? WithButtonTableViewCell  {

            let labelIsTruncated: Bool = btnCell.titleLabel.isTruncated()

            btnCell.expandButton.isHidden = !labelIsTruncated
        }
    }
}

extension UILabel {

    func countLabelLines() -> Int {
        self.layoutIfNeeded()
        let myText = self.text! as NSString
        let attributes = [NSAttributedStringKey.font : self.font!]

        let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
        return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
    }

    func isTruncated() -> Bool {

        if (self.countLabelLines() > self.numberOfLines) {
            return true
        }
        return false
    }
}

import UIKit

class WithButtonTableViewCell: UITableViewCell {

    var cellIsExpand: Bool = false

    let titleLabel: UILabel = { () -> UILabel in
        let ui = UILabel()
        ui.textColor = UIColor.black
        ui.numberOfLines = 2
        return ui
    }()

    let expandButton: UIButton = { () -> UIButton in
        let ui = UIButton()
        ui.setTitleColor(UIColor.blue, for: .normal)
        return ui
    }()

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

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

        loadUI()
        loadLayout()
    }

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

    }

    func loadUI() {

        self.addSubview(titleLabel)
        self.addSubview(expandButton)
    }

    func loadLayout() {

        titleLabel.snp.makeConstraints { (make) in
            make.top.left.equalTo(15)
            make.right.equalTo(-15)
        }

        expandButton.snp.makeConstraints { (make) in
            make.top.equalTo(titleLabel.snp.bottom).offset(10)
            make.left.equalTo(10)
            make.right.equalTo(-15)
            make.bottom.equalTo(-15)            
        }
    }
}

I see it's a perfect job for a UIStackView , as setting

self.expandButton.isHidden = true

will collpase the content , otherwise hook the height constraint of the button and set it to

btnCell.btnHCon?.constant = 0
btnCell.layoutIfNeeded()

when you want to hide it


var btnHCon: Constraint? = nil

expandButton.snp.makeConstraints { (make) in
    make.top.equalTo(titleLabel.snp.bottom).offset(10)
    make.left.equalTo(10)
    make.right.equalTo(-15)
    make.bottom.equalTo(-15)  
    self.btnHCon = make.height.equalTo(40).constraint

}

Proper Use of CellForRowAtIndexPath and WillDisplayCell, In iOS development, UITableView works with two methods related to For this you can use tableView:willDisplayCell:forRowAtIndexPath: method which can be and willDisplayCell methods are called within the same layout cycle, thereby permitting the delegate to customize the cell object before it is  The TableView control is designed to visualize an unlimited number of rows of data, broken out into columns. A TableView is therefore very similar to the ListView control, with the addition of support for columns. For an example on how to create a TableView, refer to the 'Creating a TableView' control section below.

Give height constraint to Show More button and set it as 0 when you don't want to show it.

tableView(_:willDisplay:forRowAt:), optional func tableView(_ tableView: UITableView, willDisplay cell: uses cell to draw a row, thereby permitting the delegate to customize the cell object before This method gives the delegate a chance to override state-based properties set  TableView exposes the Intent property, which can be set to any of the TableIntent enumeration members: Data – for use when displaying data entries. Note that ListView may be a better option for scrolling lists of data.

Just like the first comment said: you should update layout when you are using self-sizing cell. So you should set a height constraint to let the cell's contentView know it and the cell will resize when its subviews' constraint change. I download your code and modify it below:

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

    let cell = tableView.dequeueReusableCell(withIdentifier: cellWithButton, for: indexPath) as! WithButtonTableViewCell

    cell.titleLabel.text = textArray[indexPath.row]
    cell.expandButton.addTarget(self, action: #selector(btnPressed), for: .touchUpInside)
    cell.expandButton.tag = indexPath.row

    if expandingStateArray[indexPath.row] {
        cell.titleLabel.numberOfLines = 0
        cell.expandButton.setTitle("Close.", for: .normal)
    }else{
        cell.titleLabel.numberOfLines = 2
        cell.expandButton.setTitle("Show More.", for: .normal)
    }

    let btnCell = cell
    let labelIsTruncated: Bool = btnCell.titleLabel.isTruncated()
    if !labelIsTruncated {
        btnCell.expandButton.snp.updateConstraints { (make) in
            make.height.equalTo(0)
        }
    }
    btnCell.expandButton.isHidden = !labelIsTruncated

    return cell
}

func loadLayout() {

    titleLabel.snp.makeConstraints { (make) in
        make.top.left.equalTo(15)
        make.right.equalTo(-15)
    }

    expandButton.snp.makeConstraints { (make) in
        make.top.equalTo(titleLabel.snp.bottom).offset(10)
        make.left.equalTo(10)
        make.right.equalTo(-15)
        make.bottom.equalTo(-15)
        make.height.equalTo(100)
    }
}

It works. And one last you should't change cell's subviews' layout in the cellDisplay method.

Deprecated: I think it's the problem of auto layout strategy, you set the label's and button's layout below:

    func loadLayout() {

    titleLabel.snp.makeConstraints { (make) in
        make.top.left.equalTo(15)
        make.right.equalTo(-15)
    }

    expandButton.snp.makeConstraints { (make) in
        make.top.equalTo(titleLabel.snp.bottom).offset(10)
        make.left.equalTo(10)
        make.right.equalTo(-15)
        make.bottom.equalTo(-15)
    }
}

It will only be called when the cell is initialized and when you refresh the tableview and set the button hidden, the constraint won't be changed. I recommend you calculate this cell's height mannually. As you are using automatic cell height, there must be some advanced way to maintain self-sizing. I am also learning this part, and I'll tell you when I find a perfect way. I hope this could be helpful for you.

Configuring the Cells for Your Table, In your tableView(_:cellForRowAt:) method, configure the content of your cell Those properties contain views, but the cell object only assigns a view if the The following illustration shows a cell with a custom layout and formatting for its views. If the row heights are not all the same, or can change dynamically, provide  The JavaFX TableView enables you to display table views inside your JavaFX applications. The JavaFX TableView is represented by the class javafx.scene.control.TableView. Here is a screenshot of a JavaFX TableView: Classes Related to TableView. The JavaFX TableView class uses a set of related classes to do its job. These classes are: TableColumn

Creating Custom Table View Cells, When we're done, each cell will display's the note's title and last modified timestamp. Before we create our custom cell, let's adjust the row height for each cell in taller gives us a little more room to layout our UI for each note table view cell. With our cell's layout setup correctly, it's time to add some styling to our labels. A table view controller also relies on the use of these components, behind-the scenes: A table view delegate, which is responsible for managing the layout of the table view and responding to user interaction events. A table view delegate is an instance of the UITableViewDelegate class.

Self-Sizing Table View Cells in Practice, This enables the table view to calculate layouts quickly, which was important on old, self-sizing cells: with a built-in layout, by setting up layout constraints that can Layout constraints seem to be the most well-discussed method, but this isn'​t implement a cell that shows a switch to the side of the text when using smaller​  The table-layout property defines the algorithm used to lay out table cells, rows, and columns. Tip: The main benefit of table-layout: fixed; is that the table renders much faster. On large tables, users will not see any part of the table until the browser has rendered the whole table.

Pro iOS Table Views and Collection Views, Implementing the Custom Layout Functions . current as it's possible to be when covering a dynamic and rapidly-changing world – when things change, updates and The Table View properties HUD will now show that both the dataSource and the delegate are If you don't override this method, the cell will display the. To set this property via CSS, use the -fx-fixed-cell-size property. This should not be confused with the -fx-cell-size property. The difference between these two CSS properties is that -fx-cell-size will size all cells to the specified size, but it will not enforce that this is the only size (thus allowing for variable cell sizes, and

Comments
  • I can't find btnCell.btnHCon?.constant = 0 , and I use xcode9.4.1, snapKit 4.0.0
  • and get an erroe 'constant' is inaccessible due to 'private' protection level
  • Have any example to me?
  • I remember I try to let labelIsTruncated in "cellForRow", and the labelIsTruncated always get true. so, I change to let labelIsTruncated in "willDisplay"