Add headers and footers to tableview sections
In the previous post, we saw how to add header and footer views to entire table view. In this post, we will see how to add header and footers to individual UITableView
sections
Before we get started, make sure (Either programmatically or through storyboard) to add UITableView
instance to your main view and pin all the edges to the superView
.
Next, we will create the MyHeaderView
and MyFooterView
to act as header and footer of given TableView section respectively. They both are subclass of UITableViewHeaderFooterView
since following method,
tableView.dequeueReusableHeaderFooterView(withIdentifier: <identifier>)
returns the instance which is UITableViewHeaderFooterView
subclass.
class MyHeaderView: UITableViewHeaderFooterView {
let label = UILabel(frame: .zero)
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
let customBackgroundView = UIView(frame: .zero)
customBackgroundView.translatesAutoresizingMaskIntoConstraints = false
customBackgroundView.backgroundColor = .green
contentView.addSubview(customBackgroundView)
customBackgroundView.addSubview(label)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: customBackgroundView.leadingAnchor, constant: 16.0),
label.trailingAnchor.constraint(equalTo: customBackgroundView.trailingAnchor, constant: -16.0),
label.topAnchor.constraint(equalTo: customBackgroundView.topAnchor),
label.bottomAnchor.constraint(equalTo: customBackgroundView.bottomAnchor),
customBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
customBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
customBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor),
customBackgroundView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
}
func apply(text: String) {
label.text = text
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Please note following things,
- We are using
customBackgroundView
to color given header or footer view since iOS no longer allow us to directly setbackgroundColor
property onUITableViewHeaderFooterView
subclass - We will utilize
reuseIdentifier
to save precious memory in case there are many sections and header/footer view may potentially be re-used - We will set the background color of header view to
green
and text toblue
to be able to distinguish header from rest of the content MyFooterView
is created the same way. Only distinguishing feature - It's with the backgroundorange
and text color ofblack
Next we register both MyHeaderView
and MyFooterView
to tableView
in our viewDidLoad
method.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyHeaderView.self, forHeaderFooterViewReuseIdentifier: "header")
tableView.register(MyFooterView.self, forHeaderFooterViewReuseIdentifier: "footer")
}
Now assuming you've already registered cells in tableView, implement following dataSource methods to make tableView show some cells and section,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func numberOfSections(in tableView: UITableView) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row \(indexPath.row) Section \(indexPath.section)"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44.0
}
Now is the time to set up UITableViewDelegate
methods to properly display header and footer views for each section,
We will implemented following methods to pick heights for header and footer,
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 44.0
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
return 44.0
}
Please note that we are using UITableView.automaticDimension
for height and value of 44.0
as an estimated height. This is because we want both header and footers to support dynamic sized text where they can grow as text content or size increases.
Now we are entering the final stage. Here we will dequeue cells with identifiers that were previously used to register header and footer view, configure them with arbitrary string and return that view.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? MyHeaderView
header?.configure(text: "Header for Section \(section)\nSomething\nasdasdad")
return header
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: "footer") as? MyFooterView
footer?.configure(text: "Footer for Section \(section)\nSomething\nasdasdad")
return footer
}
I have seen some tutorials where author simply resorts to creating new views inside ofviewForHeaderInSection
orviewForFooterInSection
methods. This is not an optimal solution. If you've many sections in the tableView, you are essentially creating new view every time to act as a header or footer rather than re-using existing ones
So after all our efforts, our tableView is ready with rows, sections - And most importantly header and footers for each of those sections
And that is all I had to write about header and footer views in TableViews
. If you haven't already checked, you can refer to my other post which talks about adding header and footer views to entire tableView
.
Questions? Please reach out to me on Twitter!
References: