18

UITableView provides the methods indexPathsForVisibleRows and visibleCells, but how can I get the visible sections?

jszumski
  • 7,430
  • 11
  • 40
  • 53
iXcoder
  • 1,564
  • 4
  • 20
  • 41

12 Answers12

23

Or the really easy way would be to take advantage of valueForKeyPath and the NSSet class:

NSSet *visibleSections = [NSSet setWithArray:[[self.tableView indexPathsForVisibleRows] valueForKey:@"section"]];

Basically you get an array of the section values in the visible rows and then populate a set with this to remove duplicates.

Christopher King
  • 1,691
  • 23
  • 28
  • 1
    What a beautifully simple answer. Thanks! – ArtSabintsev Nov 12 '14 at 17:42
  • 1
    @MikkelSelsøe If there are no rows in the section then the section isn't visible either... so I'm not sure what you're saying doesn't work. – Christopher King Mar 04 '15 at 14:18
  • 3
    @ChristopherKing you can have section headers with no section rows. There are use cases for that. This answer solves that case: http://stackoverflow.com/questions/15204328/how-to-retrieve-all-visible-table-section-header-views/23538021#23538021 – Mikkel Selsøe Mar 04 '15 at 14:21
  • 4
    Very nice! I think a refinement that's a little easier on the eyes is to use the [Collection Operator](http://nshipster.com/kvc-collection-operators/) `@distinctUnionOfObjects`, which saves creating the `NSSet`. That gives: `[[self.tableView indexPathsForVisibleRows] valueForKeyPath: @"@distinctUnionOfObjects.section"]`. – Benjohn Sep 25 '15 at 10:33
  • What would be swift equivalent of it ? – itsji10dra Apr 03 '16 at 08:41
  • @itsji10dra, (tableView.indexPathsForVisibleRows! as NSArray).value(forKeyPath: "section") – OhadM Aug 04 '17 at 13:42
12

Swift version

if let visibleRows = tableView.indexPathsForVisibleRows {
    let visibleSections = visibleRows.map({$0.section})
}
Marco
  • 6,692
  • 2
  • 27
  • 38
AAV
  • 3,785
  • 8
  • 32
  • 59
8

UITableViews store their cells using an NSIndexPath. As a result there is no object for sections. Using the following code we can traverse the table and perform operations using the indexes of visible sections (I'm not sure why you want visible sections since visible only means they are currently on the screen, but whatever).

for (NSIndexPath* i in [yourTableViewName indexPathsForVisibleRows])
{
  NSUInteger sectionPath = [i indexAtPosition:0];
  //custom code here, will run multiple times per section for each visible row in the group
}
Tozar
  • 976
  • 9
  • 20
  • 3
    but if no cells in the sections then the method 'indexPathsForVisibleRows' will return null ..... – iXcoder Nov 23 '10 at 05:29
  • I want know whether the section is visible on screen then I can free or create it in dynamic or else it have to be retain that will eat lot of memory ..... – iXcoder Nov 23 '10 at 05:30
  • I added an answer that handles sections with no cells here http://stackoverflow.com/a/23538021/895099 – adamsiton May 08 '14 at 09:46
2

Extract the sections from the list of visible rows:

NSArray *indexPathsForVisibleRows = [tableView indexPathsForVisibleRows];
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
for ( NSIndexPath *indexPath in indexPathsForVisibleRows ) {
     [indexSet addIndex:indexPath.section];
}
NSLog(@"indexSet %@",indexSet);
// indexSet <NSMutableIndexSet: 0x11a5c190>[number of indexes: 5 (in 1 ranges), indexes: (9-13)]

Or:

NSArray *indexPathsForVisibleRows = [detailTableView indexPathsForVisibleRows];
NSMutableSet *sectionSet = [NSMutableSet set];
for ( NSIndexPath *indexPath in indexPathsForVisibleRows ) {
    [sectionSet addObject:[NSNumber numberWithInt:indexPath.section]];
}
NSLog(@"sectionSet %@",sectionSet);
// sectionSet {(13, 11, 9, 10, 12 )}
Jeff
  • 2,659
  • 1
  • 22
  • 41
1

2 step solution to get the visible sections in a UITableView:

1) Add the header views to a mutable array in viewForHeaderInSection
2) Update the array when the tableview scrolls in scrollViewDidScroll

note the use of the tag property to hold the section number

@property (nonatomic, strong, readwrite) NSMutableArray *headerArray;

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 40)];
    headerView.backgroundColor = [UIColor greenColor];
    headerView.tag = section;
    [_headerArray addObject:headerView];
    return headerView;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [self updateHeaderArray];
    NSLog(@"------------");
    for (UIView *view in _headerArray) {
        NSLog(@"visible section:%d", view.tag);
    }
}

- (void)updateHeaderArray {
    // remove invisible section headers
    NSMutableArray *removeArray = [NSMutableArray array];
    CGRect containerRect = CGRectMake(_tableView.contentOffset.x, _tableView.contentOffset.y,
                                      _tableView.frame.size.width, _tableView.frame.size.height);
    for (UIView *header in _headerArray) {
        if (!CGRectIntersectsRect(header.frame, containerRect)) {
            [removeArray addObject:header];
        }
    }
    [_headerArray removeObjectsInArray:removeArray];
}
cbartel
  • 1,298
  • 1
  • 11
  • 18
1

Answer is a lot simpler and neater with kvc

NSArray *visibleSections = [self.tableView.indexPathsForVisibleRows valueForKey:@"section"];

this might give you an array with duplicate values but you can manage from there.

geekay
  • 1,655
  • 22
  • 31
1
for (NSUInteger section = 0; section < self.tableView.numberOfSections; ++section) {
    UIView *headerView = [self.tableView headerViewForSection:section];
    if (headerView.window) {
        NSLog(@"its visible");
    }
}
Undo
  • 25,519
  • 37
  • 106
  • 129
alex1704
  • 479
  • 5
  • 8
1

I have got the solution.

First step, each section will show a UIView that created by - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section, that will be stored into array.

When the TableView is scrolled , I want free the invisible section view, so I need know which section is visible or not, follow function code will detect for this purpose, if the view is visible then free it.

-(BOOL)isVisibleRect:(CGRect)rect containerView:(UIScrollView*)containerView
{
    CGPoint point = containerView.contentOffset;
    CGFloat zy = point.y ;

    CGFloat  py =  rect.origin.y + rect.size.height;
    if (py - zy <0) {
            return FALSE;
    }
    CGRect  screenRect = containerView.frame;

    CGFloat by = screenRect.size.height + zy ;
    if (rect.origin.y > by) {
            return FALSE;
    }
    return TRUE;
}

(rect is the frame of the section UIView; containerView is the UITableView)

In this way, I can get visible sections of the UITableView, but I hope the SDK can provide API for this purpose directly.

jszumski
  • 7,430
  • 11
  • 40
  • 53
iXcoder
  • 1,564
  • 4
  • 20
  • 41
0

another solution, use 1 bit in your section header view's tag, like that

#define _TBL_TAG_SECTION(_TAG) ((_TAG)|(1<<30))
#define _TBL_TAG_CLEAR(_TAG) ((_TAG)&((1<<30)-1))
#define _TBL_TAG_IS_SECTION(_TAG) ((_TAG)>>30)

- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // alloc header view
    UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
    header.tag = _TBL_TAG_SECTION(section);
    return header;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect r = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y,
                      CGRectGetWidth(scrollView.frame),
                      CGRectGetHeight(scrollView.frame));
    for (UIView *v in [_tableView subviews]) {
        if ( CGRectIntersectsRect(r, v.frame) ) {
            if ( _TBL_TAG_IS_SECTION(v.tag) ) {
                NSLog(@"visible section tag %d", _TBL_TAG_CLEAR(v.tag));
            }
        }
    }
}
0

Swift 4.1

self.sections.indices.forEach{ (i:Int) in
    let section:UIView? = self.tableView(self, viewForHeaderInSection: i)
}
Sentry.co
  • 5,355
  • 43
  • 38
0

Swift 4 and Swift 5

We can do the following since an empty section could still have a visible header view:

extension UITableView {

    var indexesOfVisibleSections: [Int] {
        var indexes = [Int]()
        (0 ..< numberOfSections).forEach {
            let headerRect = (style == .plain ? 
                              rect(forSection: $0) : 
                              rectForHeader(inSection: $0))
            // The "visible part" of the tableView is based on the
            // content offset and the tableView's size.
            let visiblePartOfTableView =
                CGRect(x: contentOffset.x, y: contentOffset.y,
                       width: bounds.size.width, height: bounds.size.height)
            if (visiblePartOfTableView.intersects(headerRect)) {
                indexes.append($0)
            }
        }
        return indexes
    }

}
rolling_codes
  • 15,174
  • 22
  • 76
  • 112
Rashid Latif
  • 2,809
  • 22
  • 26
-1

Have you tried this in Swift 4?

let sections = tableView.indexPathsForVisibleRows?.map { $0.section } ?? []
for section in sections { 
    print(String(format: "%d", section)) 
}
rolling_codes
  • 15,174
  • 22
  • 76
  • 112