-
-
Save alexmx/3bd217b25542fc3dd41fa79cfe2a22c7 to your computer and use it in GitHub Desktop.
I don't know why I couldn't make the tableFooterView
work, so I used the section footer instead.
I know it is a different approach, but it may help someone.
Have in mind that, for this approach to work, you need to have static sections and cell's height.
// Add this to your UITableViewDataSource
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
// I have a dictionary called sections ([Int: [CellInfo]]) that contains all my TableView's infos
if section == sections.count - 1 {
let minHeight: CGFloat = 80 // TODO: Change for your own footer min height
var rowsSize: CGFloat = 0
var sectionSeparatorsSize: CGFloat = 0
sections.keys.forEach({ (key) in
sectionSeparatorsSize += (tableView.sectionFooterHeight + tableView.sectionHeaderHeight)
rowsSize += tableView.rowHeight * CGFloat(sections[key]?.count ?? 0)
})
let fitHeight = tableView.frame.height - tableView.adjustedContentInset.top - sectionSeparatorsSize - rowsSize
return (fitHeight > minHeight) ? fitHeight : minHeight
}
return tableView.sectionFooterHeight
}
This is great. FWIW I had to replace
self.contentInset.top
withself.adjustedContentInset.top
to get it working for me.
I also tweaked it to allow Auto Layout to determine the minHeight. My swift version looks like this:func adjustFooterViewHeightToFillTableView() { // Invoke from UITableViewController.viewDidLayoutSubviews() if let tableFooterView = self.tableFooterView { let minHeight = tableFooterView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height let currentFooterHeight = tableFooterView.frame.height let fitHeight = self.frame.height - self.adjustedContentInset.top - self.contentSize.height + currentFooterHeight let nextHeight = (fitHeight > minHeight) ? fitHeight : minHeight if (round(nextHeight) != round(currentFooterHeight)) { var frame = tableFooterView.frame frame.size.height = nextHeight tableFooterView.frame = frame self.tableFooterView = tableFooterView } } }
Thank you !!! That's a very nice way to achieve a behavior I needed. I made a few changes so it can be more SwiftLint compliant :
func adjustFooterViewHeightToFillTableView() {
// Invoke from UITableViewController.viewDidLayoutSubviews()
guard let tableFooterView = self.tableFooterView else { return }
let minHeight = tableFooterView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
let currentFooterHeight = tableFooterView.frame.height
let fitHeight = self.frame.height - self.adjustedContentInset.top - self.contentSize.height + currentFooterHeight
let nextHeight = (fitHeight > minHeight) ? fitHeight : minHeight
// No height change needed ?
guard round(nextHeight) != round(currentFooterHeight) else { return }
var frame = tableFooterView.frame
frame.size.height = nextHeight
tableFooterView.frame = frame
self.tableFooterView = tableFooterView
}
Edited @Heidan34's comment to take into account Safe Area insets:
func adjustFooterViewHeightToFillTableView() {
// Invoke from UITableViewController.viewDidLayoutSubviews()
guard let tableFooterView = self.tableFooterView else { return }
let minHeight = tableFooterView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
let currentFooterHeight = tableFooterView.frame.height
let safeAreaBottomHeight = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0.0
let fitHeight = self.frame.height - self.adjustedContentInset.top - self.contentSize.height + currentFooterHeight - safeAreaBottomHeight
let nextHeight = (fitHeight > minHeight) ? fitHeight : minHeight
// No height change needed ?
guard round(nextHeight) != round(currentFooterHeight) else { return }
var frame = tableFooterView.frame
frame.size.height = nextHeight
tableFooterView.frame = frame
self.tableFooterView = tableFooterView
}
Prior solutions were additive to the footer height when viewDidLayoutSubviews is called multiple times, causing the footer to become larger than intended. Maybe it worked fine in a UITableView subclass/extension, but not in a UITableViewController (my usage).
Here's the updated logic applied directly in a UITableViewController context:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Fill any empty content space with the footer view, pushing it to the bottom of the screen.
fillContentGap:
if let tableFooterView = tableView.tableFooterView {
/// The expected height for the footer under autolayout.
let footerHeight = tableFooterView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
/// The amount of empty space to fill with the footer view.
let gapHeight: CGFloat = tableView.bounds.height - tableView.adjustedContentInset.top - tableView.adjustedContentInset.bottom - tableView.contentSize.height
// Ensure there is space to be filled
guard gapHeight.rounded() > 0 else { break fillContentGap }
// Fill the gap
tableFooterView.frame.size.height = gapHeight + footerHeight
}
}
Of course, make sure your constraints on your footer content are sufficient to keep it pinned to the view's bottom edge as its height expands.
It's also worth noting that the Swift compiler has a time with the lengthy gapHeight
calculation, which is why I explicitly declared its type. In my usage I have an extension var vertical: CGFloat { top + bottom }
on UIEdgeInsets which helps reduce this line a bit more.
This is great. FWIW I had to replace
self.contentInset.top
withself.adjustedContentInset.top
to get it working for me.I also tweaked it to allow Auto Layout to determine the minHeight. My swift version looks like this: