how to retrieve all visible table section header views

I really liked @adamsiton's solution and I ended up translating it to swift. Here it is, FYI.

I called the file UITableView+VisibleSections.swift

import UIKit

public extension UITableView {

    var indexesOfVisibleSections: [Int] {
        // Note: We can't just use indexPathsForVisibleRows, since it won't return index paths for empty sections.
        var visibleSectionIndexes = [Int]()

        for i in 0..<numberOfSections {
            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 actualy rect is visible.
            if (self.style == .plain) {
                headerRect = rect(forSection: i)
            } else {
                headerRect = rectForHeader(inSection: 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: contentOffset.x, y: contentOffset.y, width: bounds.size.width, height: bounds.size.height)
                if (visiblePartOfTableView.intersects(headerRect!)) {
                    visibleSectionIndexes.append(i)
                }
            }
        }
        return visibleSectionIndexes
    }

    var visibleSectionHeaders: [UITableViewHeaderFooterView] {
        var visibleSects = [UITableViewHeaderFooterView]()
        for sectionIndex in indexesOfVisibleSections {
            if let sectionHeader = headerView(forSection: sectionIndex) {
                visibleSects.append(sectionHeader)
            }
        }

        return visibleSects
    }
}

For a plain style table, you can get the visible rows. From those, get the set of visible sections. And from that, get the section header views from the table.

NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
NSMutableIndexSet *sections = [[NSMutableIndexSet alloc] init];
for (NSIndexPath *indexPath in visibleRows) {
    [sections addIndex:indexPath.section];
}

NSMutableArray *headerViews = [NSMutableArray array];
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
    UIView *view = [self.tableView headerViewForSection:idx];
    [headerViews addObject:view];
}];

Note: code not tested - may contain typos. This won't work 100% for a grouped style table.


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