I have a custom UIView that is used as the title of a UITableView section. I want to be able to expand and collapse the title view by changing its height and animating this change.
The header view contains two labels that it uses with restrictions. The height limit for the label below will be set to 0 when the view is minimized.
When I click the collapse / expand button, I change the headerHeight variable, which returns tableView:heightForHeaderInSection: Here I also change the value of the height limit. I do both in tableView.beginUpdates() and tableView.endUpdates() , but the title will not animate. He won't even use the new height until I start scrolling through the table view. As an alternative, I use tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic) , which animates the height of the presentation of the header, but interferes with the subviews inside (the top label is stretched vertically throughout the view of the header, even if it has a fixed height) .
Does anyone have a solution that properly animates both the height of the presentation of the header and its subselects using constraints?
The following is the HeaderView code:
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 https://github.com/lammertw/DynamicSectionHeader project with code.