Martin Koles
Martin Koles

Reputation: 5247

Scroll UICollectionView to section header view

The code below scrolls to the right cell in UICollectionView, but the section header view is hidden behind UINavigationBar in my case. I believe I should use scrollRectToVisible instead and my question is, what is the right way to calculate the CGRect (y position) when the numberOfRows in a given section is variable.

- (void)scrollToPricing:(NSUInteger)row {

    [self.collectionView scrollToItemAtIndexPath:
      [NSIndexPath indexPathForItem:0 
                          inSection:row] 
                   atScrollPosition:UICollectionViewScrollPositionTop 
                           animated:YES];
}

Upvotes: 31

Views: 24580

Answers (9)

pixelfreak
pixelfreak

Reputation: 17834

Seems like all the answers are overly complex. This works for me:

let attributes = self.collectionView.collectionViewLayout.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: NSIndexPath(forItem: 0, inSection: section))

self.collectionView.setContentOffset(CGPointMake(0, attributes!.frame.origin.y - self.collectionView.contentInset.top), animated: true)

Swift 5:

if let attributes = collectionView.collectionViewLayout.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: section)) {
    collectionView.setContentOffset(CGPoint(x: 0, y: attributes.frame.origin.y - collectionView.contentInset.top), animated: true)
}

Upvotes: 36

Gurunath Sripad
Gurunath Sripad

Reputation: 1398

Faced a similar issue initially until I realized that the UICollectionView.ScrollPosition can be used to position the header in the collection view frame.

You just have to use the UICollectionViewScrollPositionCenteredVertically option for the header to be visible at the center of the collection view frame.

Here's how I have achieved this in Swift 4.2:

let scrollableSection = IndexPath.init(row: 0, section: indexPath.item)

emojiCollectionView.scrollToItem(at: scrollableSection, at: .centeredVertically, animated: true)

Upvotes: 1

RunLoop
RunLoop

Reputation: 20376

I found that the accepted answer no longer works properly (2 headers sticking together), especially when the collection view has to be scrolled up (down still works fine).

The following code works perfectly in either direction:

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UICollectionViewLayoutAttributes *headerAttribs = [iconsCV layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
UICollectionViewLayoutAttributes *attribs = [iconsCV layoutAttributesForItemAtIndexPath:indexPath];
CGPoint topOfHeader = CGPointMake(0, attribs.frame.origin.y -collectionV.contentInset.top -headerAttribs.bounds.size.height);
[collectionV setContentOffset:topOfHeader animated:YES];

Upvotes: 1

Yitzchak
Yitzchak

Reputation: 3416

Based on @pixelfreak answer

Swift 4.2 + iOS 11 Safe Area SUPPORT (for iPhone X and above)

if let attributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: section)) {
    var offsetY = attributes.frame.origin.y - collectionView.contentInset.top
    if #available(iOS 11.0, *) {
        offsetY -= collectionView.safeAreaInsets.top
    }
    collectionView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: true) // or animated: false
}

HAPPY CODING

Upvotes: 11

Eddy Liu
Eddy Liu

Reputation: 1461

The easiest way is to use the section header frame.origin.x and frame.origin.y and then add up section header height with the item height to create a CGRect to scroll to.

let sectionHeaderAttributes: UICollectionViewLayoutAttributes = self.collectionView.layoutAttributesForItem(at: scrollToIndexPath)!;
let itemAttributes: UICollectionViewLayoutAttributes = self.collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionElementKindSectionHeader, at: scrollToIndexPath)!;

let combinedFrame: CGRect = CGRect(x: sectionHeaderAttributes.frame.origin.x, y: sectionHeaderAttributes.frame.origin.y, width: sectionHeaderAttributes.frame.width, height: sectionHeaderAttributes.frame.height + itemAttributes.frame.height);

collectionView.scrollRectToVisible(combinedFrame, animated: false);

Upvotes: 2

Cherpak Evgeny
Cherpak Evgeny

Reputation: 2770

// scroll to selected index
NSIndexPath* cellIndexPath = [NSIndexPath indexPathForItem:0 inSection:sectionIndex];
UICollectionViewLayoutAttributes* attr = [self.collectionView.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:cellIndexPath];
UIEdgeInsets insets = self.collectionView.scrollIndicatorInsets;

CGRect rect = attr.frame;
rect.size = self.collectionView.frame.size;
rect.size.height -= insets.top + insets.bottom;
CGFloat offset = (rect.origin.y + rect.size.height) - self.collectionView.contentSize.height;
if ( offset > 0.0 ) rect = CGRectOffset(rect, 0, -offset);

[self.collectionView scrollRectToVisible:rect animated:YES];

Upvotes: 1

Sandip Patel - SM
Sandip Patel - SM

Reputation: 3394

// [myCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:index_of_sec] atScrollPosition:UICollectionViewScrollPositionTop animated:YES];

Below code will fix your problem, as i have faced same issue and used this solution developed by me instead of above commented code.

UICollectionViewLayoutAttributes *attributes = [myCollectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:index_of_sec]];
        CGRect rect = attributes.frame;
        [myCollectionView setContentOffset:CGPointMake(myCollectionView.frame.origin.x, rect.origin.y - HEIGHT_OF_YOUR_HEADER) animated:YES];

Upvotes: 2

Michael DiStefano
Michael DiStefano

Reputation: 594

First, get the frame for the header in the section:

- (CGRect)frameForHeaderForSection:(NSInteger)section {
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:1 inSection:section];
    UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
    CGRect frameForFirstCell = attributes.frame;
    CGFloat headerHeight = [self collectionView:_collectionView layout:_layout referenceSizeForHeaderInSection:section].height;
    return CGRectOffset(frameForFirstCell, 0, -headerHeight);
}

Note: I just put a value of 1 for the indexPath.item. You might need to change this to something appropriate for your implementation.

Then, scroll the UIScrollView to the point at the top of the header:

- (void)scrollToTopOfSection:(NSInteger)section animated:(BOOL)animated {
    CGRect headerRect = [self frameForHeaderForSection:section];
    CGPoint topOfHeader = CGPointMake(0, headerRect.origin.y - _collectionView.contentInset.top);
    [_collectionView setContentOffset:topOfHeader animated:animated];
}

Note: you must subtract the contentInset, otherwise it will be discarded and your scrollview will scroll behind the status bar and/or navigation bar.

Upvotes: 5

Sunny Shah
Sunny Shah

Reputation: 13020

I think this may help you

UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];

Then you can access the location through attributes.frame

Upvotes: 11

Related Questions