'Detect when UITableView section header snaps to the top of the screen
I want to detect when the UITableView section header snaps on top of the screen and then modify header's height, but I have no idea how to do that. Can anyone help?
Solution 1:[1]
I was able to accomplish detecting which header is stuck to top of the table view by using the didEndDisplayingHeaderView and willDisplayHeaderView delegate methods. The idea here is that the while we are scrolling through the sections of the table view, the rows around the header are becoming visible and we can grab the index paths of all visible rows using tableView.indexPathsForVisibleRows. Depending on if we are scrolling up or down, we compare the first or last indexPath on screen to the index path of the view that just appeared/disappeared.
//pushing up
func tableView(_ tableView: UITableView,
didEndDisplayingHeaderView view: UIView,
forSection section: Int) {
//lets ensure there are visible rows. Safety first!
guard let pathsForVisibleRows = tableView.indexPathsForVisibleRows,
let lastPath = pathsForVisibleRows.last else { return }
//compare the section for the header that just disappeared to the section
//for the bottom-most cell in the table view
if lastPath.section >= section {
print("the next header is stuck to the top")
}
}
//pulling down
func tableView(_ tableView: UITableView,
willDisplayHeaderView view: UIView,
forSection section: Int) {
//lets ensure there are visible rows. Safety first!
guard let pathsForVisibleRows = tableView.indexPathsForVisibleRows,
let firstPath = pathsForVisibleRows.first else { return }
//compare the section for the header that just appeared to the section
//for the top-most cell in the table view
if firstPath.section == section {
print("the previous header is stuck to the top")
}
}
Solution 2:[2]
I went to make my comment into codes and it works like:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == yourTableView {
if yourTableView.rectForHeader(inSection: <#sectionIWant#>).origin.y <= tableView.contentOffset.y-defaultOffSet.y &&
yourTableView.rectForHeader(inSection: <#sectionIWant#>+1).origin.y > tableView.contentOffset.y-defaultOffSet.y {
print("Section Header I want is at top")
}
} else {
//Handle other scrollViews here
}
}
So you replace sectionIWant with an Int and when that section's header is at top, the print will run.Note that yourTableView is not a parameter of scrollViewDidScroll so you have to keep a reference to your UITableView elsewhere and use it here.
Alternatively if you want to be constantly updated on which section header is at top you can use:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == yourTableView {
var currentSection = 0
while !(tableView.rectForHeader(inSection: currentSection).origin.y <= (tableView.contentOffset.y-defaultOffSet.y) &&
tableView.rectForHeader(inSection: currentSection+1).origin.y > (tableView.contentOffset.y)-defaultOffSet.y) {
if tableView.contentOffset.y <= tableView.rectForHeader(inSection: 0).origin.y {
break //Handle tableView header - if any
}
currentSection+=1
if currentSection > tableView.numberOfSections-2 {
break //Handle last section
}
}
print(currentSection)
}
}
Note that in both codes there is a defaultOffSet which is the contentOffSet of the tableView on load, this is to take into account that the contentOffSet does not start at 0 in some situations - I am not sure when but I do encounter such cases before.
Solution 3:[3]
This solution works for me.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.visibleHeaders.forEach { header in
let headerOriginalFrame = self.myTableView.rectForHeader(inSection: header.tag)
let headerCurrentFrame = header.frame
let fixed = headerOriginalFrame != headerCurrentFrame
// Do things you want
}
}
Here is full source code. https://github.com/jongwonwoo/CodeSamples/blob/master/TableView/TableviewFixedHeader/Tableview/ViewController.swift
Solution 4:[4]
My solution to this problem turned out to be really working and versatile enough. I would be very glad if this helps someone
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let sectionPosition = tableView.rect(forSection: sectionIndex)
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
if let dataSource = dataSource,
scrollView.contentOffset.y >= sectionPosition.minY {
//Next section
guard dataSource.count - 1 > sectionIndex else { return }
sectionIndex += 1
} else if translation.y > 0,
scrollView.contentOffset.y <= sectionPosition.minY {
//Last section
guard sectionIndex > 0 else { return }
sectionIndex -= 1
}
Solution 5:[5]
Late to the party, but reviewing up all responses didn't solve my issue.
The following approach, borrow a little bit from each one, adding at the same time, a more general and not fixed approach.
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let visibleIndexs = self.tableView.indexPathsForVisibleRows,
!visibleIndexs.isEmpty else { return }
var visibleHeaders = [Int]()
visibleIndexs.forEach { anIndexPath in
if !visibleHeaders.contains(anIndexPath.section) {
visibleHeaders.append(anIndexPath.section)
}
}
visibleHeaders.forEach { header in
let headerView = tableView.headerView(forSection: header)
let headerOriginalFrame = self.tableView.rectForHeader(inSection: header)
if headerOriginalFrame != headerView?.frame {
//do something with top headerView
} else {
//do something else with all the others headerView(s)
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | imattice |
| Solution 2 | |
| Solution 3 | jongwon |
| Solution 4 | Alex Bro |
| Solution 5 | valvoline |
