Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically scroll to a supplementary view within UICollectionView

I am using UICollectionView to display photos in sections. Each section has a supplementary view as a header and is supplied via the method: viewForSupplementaryElementOfKind.

I have a scrubber on the side that allows the user to jump from section to section. For now I am scrolling to the first item in the section using scrollToItemAtIndexPath:atScrollPosition:animated:, but what I really want is to scroll the collectionView so that that section's header is at the top of the screen, not the first cell. I do not see an obvious method to do this with. Do any of you have a work around?

I suppose I could scroll to the first item of the section, and then offset that by the supplementary height plus the offset between the items and header if it comes down to that (there is a method for scrolling to point coordinates of the contentView). However if there is a simpler way, I'd like to know.

Thanks.

like image 729
VaporwareWolf Avatar asked Apr 26 '13 22:04

VaporwareWolf


4 Answers

You can not use scrollToItemAtIndexPath:atScrollPosition:animated for this.

Hopefully, they will add a new method like scrollToSupplementaryElementOfKind:atIndexPath: in the future, but for now, the only way is to manipulate the contentOffset directly.

The code below shows how to scroll header to be on top vertically with FlowLayout. You can do the same for horizontal scrolling, or use this idea for other layout types.

NSIndexPath *indexPath = ... // indexPath of your header, item must be 0

CGFloat offsetY = [collectionView layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath].frame.origin.y;

CGFloat contentInsetY = self.contentInset.top;
CGFloat sectionInsetY = ((UICollectionViewFlowLayout *)collectionView.collectionViewLayout).sectionInset.top;

[collectionView setContentOffset:CGPointMake(collectionView.contentOffset.x, offsetY - contentInsetY - sectionInsetY) animated:YES];

Note that if you have non-zero contentInset (as in iOS 7, when scroll views expand below bars) you need to subtract it from the offsetY, as shown. Same for sectionInset.

Update:

The code assumes that the layout is in prepared, "valid" state because it uses it to calculate the offset. The layout is prepared when the collection view presents its content.

The call to [_collectionView.collectionViewLayout prepareLayout] before the code above may help when you need to scroll the collection view which is not yet presented (from viewDidLoad say). The call to layoutIfNeeded (as @Vrasidas suggested in comments) should work too because it also prepares the layout.

like image 149
Serhii Yakovenko Avatar answered Oct 24 '22 04:10

Serhii Yakovenko


Solution in Swift,

let section: Int = 0 // Top

if let cv = self.collectionView {

    cv.layoutIfNeeded()

    let indexPath = IndexPath(row: 1, section: section)
    if let attributes =  cv.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath) {

        let topOfHeader = CGPoint(x: 0, y: attributes.frame.origin.y - cv.contentInset.top)
        cv.setContentOffset(topOfHeader, animated: true)
    }
}

Props to Gene De Lisa: http://www.rockhoppertech.com/blog/scroll-to-uicollectionview-header/

like image 22
clozach Avatar answered Oct 24 '22 05:10

clozach


// 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];
like image 32
Cherpak Evgeny Avatar answered Oct 24 '22 06:10

Cherpak Evgeny


One thing the solutions don't manage is if you are pinning section headers. The methods work fine with unpinned headers, but if your headers are pinned, while scrolling to a section above the current section, it will stop once the section header appears (which will be for the bottom row of your section). That may be desirable in some cases but I think the goal is to put the top of the section at the top of the screen.

In which case you need to take the methods above and adjust them a bit. For instance:

UICollectionView *cv = self.collectionView;
CGFloat contentInsetY = cv.contentInset.top;
CGFloat offsetY = [cv layoutAttributesForItemAtIndexPath:ip].frame.origin.y;
CGFloat sectionHeight =
[cv layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:ip].frame.size.height;
[cv setContentOffset:CGPointMake(cv.contentOffset.x, offsetY - contentInsetY - sectionHeight) animated:YES];

Now you are basically scrolling the first row of your section to visible, less the height of its section header. This will put the section header on the top where you want it with pinned headers so the direction of the scroll won't matter anymore. I didn't test with section insets.

like image 45
nobody Avatar answered Oct 24 '22 05:10

nobody