Reputation: 781
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
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
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
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
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
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
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