Reputation: 271774
I want to display half of the header at first. When the user taps on one of the buttons inside the header, the rest of the header slides down and reveals itself. When the user taps on the button again, the header slides back up to its original size (1/2 size).
However, when I tried to expand my header's height, it covers the tableView cells instead of pushing them down.
func prepareHeader(){
let headerHeight = CGFloat(51.0)
headerView = UIView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: headerHeight))
headerView.backgroundColor = UIColor.whiteColor()
headerView.layer.addSublayer(bottomBorder)
headerView.layer.masksToBounds = true
let toggler = UIButton(type: .Custom)
toggler.frame = CGRectMake(0, 0, 40, 40)
toggler.backgroundColor = FlatGreen()
toggler.addTarget(self, action: "toggleHeader:", forControlEvents: UIControlEvents.TouchUpInside)
headerView.addSubview(toggler)
self.tableView.tableHeaderView = headerView
}
func toggleHeader(sender: UIButton){
print("Pushed")
var newFrame = self.headerView.frame
newFrame.size.height = CGFloat(100)
self.headerView.frame = newFrame
}
Upvotes: 2
Views: 2152
Reputation: 12053
WHAT WE WANT
A table view header that can be dynamically opened and closed.
SOLUTION 1: WITHOUT INTERFACE BUILDER
class ViewController: UIViewController {
var headerView = UIView()
var tableView = UITableView()
var isHeaderOpen = false
let HEADER_CLOSED_HEIGHT: CGFloat = 100
let HEADER_OPEN_HEIGHT: CGFloat = 200
var headerClosedHeightConstraint: NSLayoutConstraint!
var headerOpenHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Add the table view to the screen.
view.addSubview(tableView)
// Make the table view fill the screen.
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[tableView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["tableView": tableView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[tableView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["tableView": tableView]))
// Add the header view to the table view.
tableView.tableHeaderView = headerView
headerView.backgroundColor = UIColor.redColor()
headerView.translatesAutoresizingMaskIntoConstraints = false
// Add a button to the header.
let button = UIButton(type: .Custom)
button.setTitle("Toggle", forState: .Normal)
headerView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[button]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
headerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[button]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["button": button]))
button.addTarget(self, action: "toggleHeaderAction:", forControlEvents: .TouchUpInside)
// Make the header full width.
view.addConstraint(NSLayoutConstraint(item: headerView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0))
// Create the constraints for the two different header heights.
headerClosedHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: HEADER_CLOSED_HEIGHT)
// Create the height constraint for the header's other height for later use.
headerOpenHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: HEADER_OPEN_HEIGHT)
closeHeader() // Close header by default.
}
func openHeader() {
headerView.removeConstraint(headerClosedHeightConstraint)
headerView.addConstraint(headerOpenHeightConstraint)
updateHeaderSize()
isHeaderOpen = true
}
func closeHeader() {
headerView.removeConstraint(headerOpenHeightConstraint)
headerView.addConstraint(headerClosedHeightConstraint)
updateHeaderSize()
isHeaderOpen = false
}
func updateHeaderSize() {
// Calculate the header's new size based on its new constraints and set its frame accordingly.
let size = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
var frame = headerView.frame
frame.size.height = size.height
headerView.frame = frame
tableView.tableHeaderView = headerView
self.headerView.layoutIfNeeded()
}
func toggleHeader() {
if isHeaderOpen {
closeHeader()
} else {
openHeader()
}
}
func toggleHeaderAction(sender: AnyObject) {
toggleHeader()
}
}
SOLUTION 2: USING INTERFACE BUILDER
To make a UITableView
header change height dynamically, try the following.
In your storyboard, first add a UITableView
to your view controller. Then add a UIView
as the first child of the UITableView
(Interface Builder automatically interprets this as the UITableView
's header view).
Wire up an outlet to the table and header so we can access them later in code.
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var headerView: UIView!
Next, in code we create two constraints (opened and closed) which we'll later add/remove to toggle the header's height.
var headerOpenHeightConstraint: NSLayoutConstraint!
var headerClosedHeightConstraint: NSLayoutConstraint!
Let's also create a flag to keep track of the open/closed state of the header. Also, we specify the open header height.
var isHeaderOpen = false
let HEADER_OPEN_HEIGHT: CGFloat = 200
Interface Builder automatically converts the dimensions of the header we created into Auto Layout constraints. Since we're going to change the header's height ourselves, we need to add replacement constraints of our own and remove these automatic constraints (NSAutoresizingMaskLayoutConstraints
).
In viewDidLoad()
, add the following (takes the default header height as the closed height and HEADER_OPEN_HEIGHT
as the open header height).
override func viewDidLoad() {
super.viewDidLoad()
// The header needs a new width constraint since it will no longer have the automatically generated width.
view.addConstraint(NSLayoutConstraint(item: headerView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0))
// Add the height constraint for the default state (closed header).
headerClosedHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: headerView.bounds.height)
headerView.addConstraint(headerClosedHeightConstraint)
// Make the constraint we'll use later to open the header.
headerOpenHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: HEADER_OPEN_HEIGHT)
// Finally we disable this so that we don't get Interface Builder's automatically generated constraints
// messing with our constraints.
headerView.translatesAutoresizingMaskIntoConstraints = false
}
Finally, in Interface Builder, add a button and wire it up to an action in your view controller.
@IBAction func toggleHeaderAction(sender: AnyObject) {
// Add/remove the appropriate constraints to toggle the header.
if isHeaderOpen {
headerView.removeConstraint(headerOpenHeightConstraint)
headerView.addConstraint(headerClosedHeightConstraint)
} else {
headerView.removeConstraint(headerClosedHeightConstraint)
headerView.addConstraint(headerOpenHeightConstraint)
}
// Calculate the header's new size based on its new constraints.
let size = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// Give the header its new height.
var frame = headerView.frame
frame.size.height = size.height
headerView.frame = frame
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
tableView.tableHeaderView = headerView
isHeaderOpen = !isHeaderOpen
}
Upvotes: 5
Reputation: 5188
A couple thoughts to try
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition .Top:, animated: true)
where indexPath is the cell that was previously at the top? You can experiment with it being animated or notself.tableView.headerView
again improve that?Upvotes: 0