is there a way to get all the section header views that are visible?
something similar to UITableView\'s visibleCells instance method..
A UITableView
extension that gives you the visibleHeaderViews
based on @OhadM answer (https://stackoverflow.com/a/45525595/5058116), but which checks on potential nil
values.
extension UITableView {
/// The section header views that are visible in the table view.
var visibleHeaderViews: [UITableViewHeaderFooterView] {
var headerViews = [UITableViewHeaderFooterView]()
guard let indexPaths = indexPathsForVisibleRows else { return headerViews }
for indexPath in indexPaths {
if let headerView = headerView(forSection: indexPath.section) {
headerViews.append(headerView)
}
}
return headerViews
}
}
In this case, I think you set the cell.tag
to the current section (indexPath.section
) in cellForRowAtIndexPath
and use the visibleCells
method as you described and headerViewForSection
.
All of these answers turn out to be overly complicated. All you need to do is call headerView(forSection:)
in a loop. It will return nil for header views that are not visible.
However, there is a catch: Your header views have to inherit from UITableViewHeaderFooterView
for this to work (which they technically should anyway, but everything except this works even if they don't so you can easily miss this.)
Once you have your class inheriting correctly, you can just do this:
for section in 0 ..< tableView.numberOfSections {
guard let header = tableView.headerView(forSection: section) as? CustomHeaderView else { continue }
header.update(...)
}
The solution by Benjamin Wheeler is a great Swift solution. I fixed an issue in it due to new Swift syntax, and modified it to match the .visibleCells property provided by the default UITableView implementation.
extension UITableView {
/// The table section headers that are visible in the table view. (read-only)
///
/// The value of this property is an array containing UITableViewHeaderFooterView objects, each representing a visible cell in the table view.
///
/// Derived From: [http://stackoverflow.com/a/31029960/5191100](http://stackoverflow.com/a/31029960/5191100)
var visibleSectionHeaders: [UITableViewHeaderFooterView] {
get {
var visibleSects = [UITableViewHeaderFooterView]()
for sectionIndex in indexesOfVisibleSections() {
if let sectionHeader = self.headerViewForSection(sectionIndex) {
visibleSects.append(sectionHeader)
}
}
return visibleSects
}
}
private func indexesOfVisibleSections() -> Array<Int> {
// Note: We can't just use indexPathsForVisibleRows, since it won't return index paths for empty sections.
var visibleSectionIndexes = Array<Int>()
for (var i = 0; i < self.numberOfSections; i++) {
var headerRect: CGRect?
// In plain style, the section headers are floating on the top,
// so the section header is visible if any part of the section's rect is still visible.
// In grouped style, the section headers are not floating,
// so the section header is only visible if it's actual rect is visible.
if (self.style == .Plain) {
headerRect = self.rectForSection(i)
} else {
headerRect = self.rectForHeaderInSection(i)
}
if headerRect != nil {
// The "visible part" of the tableView is based on the content offset and the tableView's size.
let visiblePartOfTableView: CGRect = CGRect(
x: self.contentOffset.x,
y: self.contentOffset.y,
width: self.bounds.size.width,
height: self.bounds.size.height
)
if (visiblePartOfTableView.intersects(headerRect!)) {
visibleSectionIndexes.append(i)
}
}
}
return visibleSectionIndexes
}
}
The problem with using indexPathsForVisibleRows is that it doesn't include sections without any rows. To get all visible section, including empty sections you have to check the rect of the section and compare it to the contentOffset of the table.
You also have to pay attention to the difference between plain style with floating sections and grouped style without floating sections.
I made a category that support this calculation:
@interface UITableView (VisibleSections)
// Returns an array of NSNumbers of the current visible section indexes
- (NSArray *)indexesOfVisibleSections;
// Returns an array of UITableViewHeaderFooterView objects of the current visible section headers
- (NSArray *)visibleSections;
@end
@implementation UITableView (VisibleSections)
- (NSArray *)indexesOfVisibleSections {
// Note: We can't just use indexPathsForVisibleRows, since it won't return index paths for empty sections.
NSMutableArray *visibleSectionIndexes = [NSMutableArray arrayWithCapacity:self.numberOfSections];
for (int i = 0; i < self.numberOfSections; i++) {
CGRect headerRect;
// In plain style, the section headers are floating on the top, so the section header is visible if any part of the section's rect is still visible.
// In grouped style, the section headers are not floating, so the section header is only visible if it's actualy rect is visible.
if (self.style == UITableViewStylePlain) {
headerRect = [self rectForSection:i];
} else {
headerRect = [self rectForHeaderInSection:i];
}
// The "visible part" of the tableView is based on the content offset and the tableView's size.
CGRect visiblePartOfTableView = CGRectMake(self.contentOffset.x, self.contentOffset.y, self.bounds.size.width, self.bounds.size.height);
if (CGRectIntersectsRect(visiblePartOfTableView, headerRect)) {
[visibleSectionIndexes addObject:@(i)];
}
}
return visibleSectionIndexes;
}
- (NSArray *)visibleSections {
NSMutableArray *visibleSects = [NSMutableArray arrayWithCapacity:self.numberOfSections];
for (NSNumber *sectionIndex in self.indexesOfVisibleSections) {
UITableViewHeaderFooterView *sectionHeader = [self headerViewForSection:sectionIndex.intValue];
[visibleSects addObject:sectionHeader];
}
return visibleSects;
}
@end
Swift 4+ Clean and simple, without duplicities:
extension UITableView {
var visibleHeaderViews: [UITableViewHeaderFooterView] {
guard let visibleSectionsIndexPaths = indexPathsForVisibleRows?.compactMap({ $0.section }) else { return [] }
return Set(visibleSectionsIndexPaths).compactMap { headerView(forSection: $0) }
}
}