Home > Net >  Show hidden label by button click inside tableView Cell (expand/collapse cell)
Show hidden label by button click inside tableView Cell (expand/collapse cell)

Time:05-19

I have a stackView(vertical) which contains labels and bottom description label is hidden by default. And I implemented an arrow button at the right side of the cell. By clicking the button, I just want to show the hidden description label and stackView should expand automatically and make cell bigger. This was my basic idea to implement expandable cell. So this is the code I used to get desired results:

@objc func downArrowButtonClicked(_ sender: UIButton){
        let indexPath = IndexPath(row: sender.tag, section: 0)
        selectedIndex = indexPath
        selectedCellIndex = sender.tag
        isDescHidden = !isDescHidden
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }

Above is the code for the button inside clicked cell. I went with the idea to reload that particular index. I created a variable named selectedCellIndex of in which I use in cellForRowAt method to make some changes.

I also had the implement some code in viewDidLayoutSubviews() as when I first clicked the cell wasn't getting expanded fully. here's that just in case:

 override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let indexPath = selectedIndex
        tableView.reloadRows(at: [indexPath ?? IndexPath(row: 0, section: 0)], with: .automatic)
    }

And calling it in willDisplay method which finally fixed the cell expansion issue:

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

And here is my cellForRowAt function:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
            
            if indexPath.row == 0 {
                cell.lblTitle.text = "Title 1"
                cell.lblDesc.text = "Desc 1"
            }
            else if indexPath.row == 1 {
                cell.lblTitle.text = "Title 2"
                cell.lblDesc.text = "Desc 2"
            }
            else {
                cell.lblTitle.text = "Title 3"
                cell.lblDesc.text = "Desc 3"
            }
            
            if selectedCellIndex != nil {
                if isDescHidden == false {
                    if cell.isDescHidden == true {
                        cell.lblDesc.isHidden = false
                        cell.btnArrow.setImage(UIImage(systemName: "chevron.up"), for: .normal)
                    }
                    else {
                        cell.lblDesc.isHidden = true
                        cell.btnArrow.setImage(UIImage(systemName: "chevron.down"), for: .normal)
                    }
                }
                else {
                    if cell.isDescHidden == true {
                        cell.lblDesc.isHidden = true
                        cell.btnArrow.setImage(UIImage(systemName: "chevron.down"), for: .normal)
                    }
                    else {
                        cell.lblDesc.isHidden = false
                        cell.btnArrow.setImage(UIImage(systemName: "chevron.up"), for: .normal)
                    }
                }
                cell.isDescHidden = !cell.isDescHidden
            }
            
            cell.btnArrow.tag = indexPath.row
            cell.btnArrow.addTarget(self, action: #selector(downArrowButtonClicked(_:)), for: .touchUpInside)
            
            return cell
    }

This approach gets too confusing as you can see from the above code. The isDescHidden variable is defined in both Main view controller as well as table view cell class and I was trying to use both to expand or collapse a particular cell. However first time it works but if I have 3 cells expanded, collapsing button click doesn't work for 1-2 clicks then works.

Is there a better approach for this kind of problem? Or is there any way I can directly set cell.isDescHidden value from @objc func downArrowButtonClicked(_ sender: UIButton) function? So I can use that in cellForRowAt function? I would be glad if I could directly make changes to cell variables from that.

CodePudding user response:

Use following function for automatic height for rows.

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

After this you can simply toggle the visibility on your stackView items, like this.

cell.detail.isHidden.toggle()

Here is my CustomCell

class CustomCell: UITableViewCell {
    @IBOutlet weak var titleCell: UILabel!
    @IBOutlet weak var detail: UILabel!
    var onArrowClick: ((UIButton)->())!
    
    @IBAction func handleArrowButton(sender: UIButton){
        onArrowClick(sender)
    }
}

For sample Data

let data = [["Nothing", "description is very long "], ["Nothing", "description is very long "], ["Nothing", "description is very long "], ["Nothing", "description is very long "], ["Nothing", "description is very long "]]

TableView methods are like this

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
        cell.titleCell.text = data[indexPath.row][0]
        cell.detail.text = data[indexPath.row][1]
        cell.onArrowClick = { button in
            let upArrow = UIImage(systemName: "chevron.up.circle.fill")
            let downArrow = UIImage(systemName: "chevron.down.circle.fill")
            let newArrowImage = button.image(for: .normal) == upArrow ? downArrow : upArrow
            button.setImage(newArrowImage, for: .normal)
            cell.detail.isHidden.toggle()
        }
        return cell
    }
    
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }

CodePudding user response:

First create a model to show data into cell. You have to preserve the state of cell.

struct CellData {
    var title: String
    var details: String
    var isExpanded: Bool
}

In CustomTableViewCell add a property for cellData and assign Outlets data from it. Also create a protocol to reload row from UIViewController

protocol CustomTableViewCellDelegate {
    func reloadRow(sender: CustomTableViewCell, flag: Bool)
}

class CustomTableViewCell: UITableViewCell {
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var detailsLabel: UILabel!
    @IBOutlet weak var showButton: UIButton!
    
    var indexPath: IndexPath?
    var delegate: CustomTableViewCellDelegate?
    
    var data: CellData? {
        didSet {
            
            if let data = data {
                if data.isExpanded == false {
                    detailsLabel.isHidden = true
                    showButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
                    
                }else {
                    detailsLabel.isHidden = true
                    showButton.setImage(UIImage(systemName: "chevron.up"), for: .normal)
                }
                
                titleLabel.text = data.title
                detailsLabel.text = data.details
            }
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        titleLabel.text = nil
        detailsLabel.text = nil
    }
    
    @IBAction func showButtonAction(_ sender: UIButton) {
        if var data = data {
            data.isExpanded.toggle()
            delegate?.reloadRow(cell: self, flag: data.isExpanded)
        }
    }
}

In UIViewController add an array of CellData type. You may assign it's data in viewDidLoad() method.

var tableData: [CellData]

Modify numberOfRowsInSection() and cellForRow() method like bleow.

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
    cell.data = tableData[indexPath.row]
    cell.delegate = self
    return cell
}

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

Then confirm CustomTableViewCellDelegate protocol to UIViewController

extension ViewController: CustomTableViewCellDelegate {
    func reloadRow(sender: CustomTableViewCell, isExpanded: Bool) {
        guard let tappedIndexPath = tableView.indexPath(for: sender) else { return }
        tableData[tappedIndexPath.row].isExpanded = isExpanded
        tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
    }
}
  • Related