David Ben Ari
David Ben Ari

Reputation: 2299

how to retrieve all visible table section header views

is there a way to get all the section header views that are visible?

something similar to UITableView's visibleCells instance method..

Upvotes: 20

Views: 14169

Answers (10)

Dag Ågren
Dag Ågren

Reputation: 1148

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(...)
}

Upvotes: 0

matuslittva
matuslittva

Reputation: 163

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) }
    }

}

Upvotes: 0

ajrlewis
ajrlewis

Reputation: 3058

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

    }

}

Upvotes: 2

OhadM
OhadM

Reputation: 4803

A quick look at the UITableView documentation gives us the indexPathsForVisibleRows and combining it with map gives us the array we need:

tableView.indexPathsForVisibleRows.map{ tableView.headerView(forSection: $0.section) }

map returns the array of our visible headers-in-section.

Upvotes: 1

Ben Wheeler
Ben Wheeler

Reputation: 7344

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
    }
}

Upvotes: 14

Mortgy
Mortgy

Reputation: 563

to get the current visible sections, u could get it by checking the current visible cells indexpath section, so this function could return to u the visible sections

- (NSArray *)indexesOfVisibleSections {

    NSMutableArray *visibleSections = [NSMutableArray array];

    for (UITableViewCell * cell in [self.tableView visibleCells]) {
        if (![visibleSections containsObject:[NSNumber numberWithInteger:[self.tableView indexPathForCell:cell].section]]) {
            [visibleSections addObject:[NSNumber numberWithInteger:[self.tableView indexPathForCell:cell].section]];
        }
    }

    return visibleSections;
}

And to access section view , u can use

- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section;

Upvotes: 1

dbburgess
dbburgess

Reputation: 761

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
    }
}

Upvotes: 2

rmaddy
rmaddy

Reputation: 318774

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.

Upvotes: 4

adamsiton
adamsiton

Reputation: 3672

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

Upvotes: 22

Marcus Adams
Marcus Adams

Reputation: 53830

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.

Upvotes: 1

Related Questions