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.
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With