Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate changing height of a UITableView section header on constraints

I have a custom UIView being used as the section header of a UITableView. I want to be able to expand and collapse the header view by changing it's height and animate this change.

The header view contains two labels which it lays out using constraints. The height constraint of a label placed at the bottom will be set to 0 when the view is collapsed.

When I press the collapse/expand button I change a headerHeight variable which is returned by tableView:heightForHeaderInSection:. Here I also change the value of the height constraint. I do both within a tableView.beginUpdates() and tableView.endUpdates() but the header view will not animate. It will not even use the new height until I begin scrolling the table view. Alternatively I use tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic) which does animate the height of the header view but messes up the subviews within (the top label becomes stretched vertically across the entire header view even though it has a fixed height).

Does anyone have a solution that properly animated both the header view height and the subviews of it using constraints?

Below is the code of the HeaderView:

class HeaderView: UIView {

    let titleLabel = UILabel()
    let subtitleLabel = UILabel()
    let expandButton = UIButton()

    var subtitleLabelHeightConstraint: NSLayoutConstraint!

    init() {
        super.init(frame: CGRectNull)

        titleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        subtitleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        expandButton.setTranslatesAutoresizingMaskIntoConstraints(false)

        expandButton.setTitle("Expand / Collapse", forState: .Normal)

        addSubview(titleLabel)
        addSubview(subtitleLabel)
        addSubview(expandButton)

        let views = ["titleLabel": titleLabel, "subtitleLabel": subtitleLabel, "expandButton": expandButton]
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[titleLabel]-[expandButton]|", options: nil, metrics: nil, views: views))
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[subtitleLabel]|", options: nil, metrics: nil, views: views))
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[titleLabel]-(>=0)-|", options: nil, metrics: nil, views: views))
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(>=0)-[subtitleLabel]|", options: nil, metrics: nil, views: views))
        addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[expandButton]-(>=0)-|", options: nil, metrics: nil, views: views))

        titleLabel.addConstraint(NSLayoutConstraint(item: titleLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 20))
        subtitleLabelHeightConstraint = NSLayoutConstraint(item: subtitleLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 20)
        subtitleLabel.addConstraint(subtitleLabelHeightConstraint)
    }

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

}

And the code of the TableViewController:

class TableViewController: UITableViewController {

    let headerView: HeaderView = {
        let view = HeaderView()
        view.titleLabel.text = "Title"
        view.subtitleLabel.text = "Subtitle"
        view.backgroundColor = UIColor.lightGrayColor()
        return view
    }()

    var headerHeight: CGFloat = 60

    override func viewDidLoad() {
        super.viewDidLoad()

        headerView.expandButton.addTarget(self, action: "toggleExpansion", forControlEvents: .TouchUpInside)
    }

    func toggleExpansion() {
        tableView.beginUpdates()

        if headerHeight == 60 {
            headerHeight = 40
            headerView.subtitleLabelHeightConstraint.constant = 0
        } else {
            headerHeight = 60
            headerView.subtitleLabelHeightConstraint.constant = 20
        }

        tableView.endUpdates()

        // Alternatively use tableView.reloadSections instead of begin and end updates:
        // tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic)
    }

    // MARK: - Table view data source

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

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
        cell.textLabel?.text = "Cell"
        return cell
    }

    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return headerHeight
    }

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return headerView
    }


}

I also created a project at https://github.com/lammertw/DynamicSectionHeader with the code.

like image 607
lammert Avatar asked May 04 '15 07:05

lammert


1 Answers

Try to replace this :

func toggleExpansion() {
    tableView.beginUpdates()

    if headerHeight == 60 {
        headerHeight = 40
        headerView.subtitleLabelHeightConstraint.constant = 0
    } else {
        headerHeight = 60
        headerView.subtitleLabelHeightConstraint.constant = 20
    }

    tableView.endUpdates()

    // Alternatively use tableView.reloadSections instead of begin and end updates:
    // tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic)
}

by this:

func toggleExpansion() {

    if headerHeight == 60 {
        headerHeight = 40
        headerView.subtitleLabelHeightConstraint.constant = 0
    } else {
        headerHeight = 60
        headerView.subtitleLabelHeightConstraint.constant = 20
    }

    tableView.beginUpdates()
    tableView.endUpdates()

    // Alternatively use tableView.reloadSections instead of begin and end updates:
    // tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic)
}
like image 177
Yehya Ch. Avatar answered Sep 18 '22 13:09

Yehya Ch.