Meinhard
Meinhard

Reputation: 781

Detect if UITableView section header is floating

is there a way to detect if a header of a section in an UITableView is currently floating? I want to scroll the table view to the header position only if it is floating.

Thanks in advance!

Upvotes: 6

Views: 5063

Answers (6)

Adam
Adam

Reputation: 1913

iOS 11.0+ solution for UITableViews as well as UICollectionViews

In both cases, use the scrollViewDidScroll method. You can use the currentHeader property to store current header displayed at the top. HEIGHT_OF_HEADER_VIEW is a value you should specify yourself.

For UITableView

private var currentHeader: UITableViewHeaderFooterView? {
    willSet {
        // Handle `old` header
    } didSet {
        // Hanlde `new` header
    }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let point = CGPoint(x: 0, y: HEIGHT_OF_HEADER_VIEW + tableView.contentOffset.y + tableView.adjustedContentInset.top)
    guard let path = tableView.indexPathForRow(at: point) else {
        return
    }

    let section = path.section
    let rect = tableView.rectForHeader(inSection: section)
    let converted = tableView.convert(rect, to: tableView.superview)
    let safeAreaInset = tableView.safeAreaInsets.top

    // Adding 1 offset because of large titles that sometimes cause slighly wrong converted values
    guard converted.origin.y <= safeAreaInset,
        let header = tableView.headerView(forSection: section) else {
        return
    }
    currentHeader = header
}

For UICollectionView

private var currentHeader: UICollectionReusableView? {
    willSet {
        // Handle `old` header
    } didSet {
        // Handle `new` header
    }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let x = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset.left ?? 0
    let point = CGPoint(x: x, y: HEIGHT_OF_HEADER_VIEW + collectionView.contentOffset.y + collectionView.adjustedContentInset.top)

    guard let path = collectionView.indexPathForItem(at: point),
        let rect = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: path.section))?.frame else {
        return
    }

    let converted = collectionView.convert(rect, to: collectionView.superview)
    let safeAreaInset = collectionView.safeAreaInsets.top

    guard converted.origin.y <= safeAreaInset,
        let header = collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: path.section)) else {
        return
    }
    currentHeader = header
}

Upvotes: 1

Chris Hinkle
Chris Hinkle

Reputation: 4407

Adding a swift port of @robdashnash's answer

extension UITableView
{
     func isFloatingSectionHeader( view:UITableViewHeaderFooterView )->Bool
     {
          if let section = section( for:view )
          {
              return isFloatingHeaderInSection( section:section )
          }
          return false
      }

     func isFloatingHeaderInSection( section:Int )->Bool
     {
         let frame = rectForHeader( inSection:section )
         let y = contentInset.top + contentOffset.y
         return y > frame.origin.y
     }

     func section( for view:UITableViewHeaderFooterView )->Int?
     {
         for i in stride( from:0, to:numberOfSections, by:1 )
         {
             let a = convert( CGPoint.zero, from:headerView( forSection:i ) )
             let b = convert( CGPoint.zero, from:view )
             if a.y == b.y
             {
                 return i
             }
         }
         return nil
     }
 }

Upvotes: 1

Hilen
Hilen

Reputation: 851

The code works for me.

- (BOOL)isSection0HeaderSticky {
    CGRect originalFrame = [self.listTableView rectForHeaderInSection:0];

    UIView *section0 = [self.listTableView headerViewForSection:0];

    if (originalFrame.origin.y < section0.frame.origin.y) {
        return  YES;
    }
    return NO;
}

Upvotes: 0

user1951992
user1951992

Reputation:

This works.

@implementation UITableView (rrn_extensions)

-(BOOL)rrn_isFloatingSectionHeaderView:(UITableViewHeaderFooterView *)view {
    NSNumber *section = [self rrn_sectionForHeaderFooterView:view];
    return [self rrn_isFloatingHeaderInSection:section.integerValue];
}

-(BOOL)rrn_isFloatingHeaderInSection:(NSInteger)section {
    CGRect frame = [self rectForHeaderInSection:section];
    CGFloat y = self.contentInset.top + self.contentOffset.y;
    return y > frame.origin.y;
}

-(NSNumber *)rrn_sectionForHeaderFooterView:(UITableViewHeaderFooterView *)view {
    for (NSInteger i = 0; i < [self numberOfSections]; i++) {
        CGPoint a = [self convertPoint:CGPointZero fromView:[self headerViewForSection:i]];
        CGPoint b = [self convertPoint:CGPointZero fromView:view];
        if (a.y == b.y) {
            return @(i);
        }
    }
    return nil;
}

@end

Upvotes: 1

zhongWJ
zhongWJ

Reputation: 9

You can first store the original rect of section header view.

 headerView.originalPoint = [tableView rectForHeaderInSection:section].origin;

And send scrollViewDidScroll:scrollView message to header.

In your header view

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
   BOOL isFloating = self.frame.origin.y > self.originalPoint.y;
   if (isFloating) {
       //DO what you want
   }
}

Upvotes: 0

jrturton
jrturton

Reputation: 119272

The header will be floating if the first cell of the section is no longer visible. So:

NSIndexPath *topCellPath = [[self.tableView indexPathsForVisibleRows] objectAtIndex:0];
if (topCellPath.row != 0)
{
    // Header must be floating!
}

You could achieve a similar effect by scrolling to the index path with scrollToRowAtIndexPath:atScrollPosition:animated: and a scroll position of UITableViewScrollPositionNone - this would not scroll if the first cell in the section was already on the screen.

Upvotes: 8

Related Questions