以编程方式滚动到UICollectionView中的补充​​视图

时间:2022-05-26 20:27:37

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.

我正在使用UICollectionView来显示部分照片。每个部分都有一个补充视图作为标题,并通过以下方法提供: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?

我的侧面有一个擦洗器,允许用户从一个区域跳到另一个区域。现在我使用scrollToItemAtIndexPath:atScrollPosition:animated:滚动到该部分的第一项,但我真正想要的是滚动collectionView,使该部分的标题位于屏幕的顶部,而不是第一个单元格。我没有看到一个明显的方法来做到这一点。你们中有人有工作吗?

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.

我想我可以滚动到该部分的第一项,然后通过补充高度加上项目和标题之间的偏移来抵消它(如果它归结为那个(有一种方法可以滚动到contentView的点坐标))。但是,如果有一种更简单的方法,我想知道。

Thanks.

谢谢。

4 个解决方案

#1


37  

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

你不能使用scrollToItemAtIndexPath:atScrollPosition:为此动画。

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.

希望他们将来会添加一个像scrollToSupplementaryElementOfKind:atIndexPath:这样的新方法,但是现在,唯一的方法就是直接操作contentOffset。

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.

下面的代码显示了如何使用FlowLayout将标题滚动到垂直顶部。您可以对水平滚动执行相同操作,或将此想法用于其他布局类型。

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.

请注意,如果您有非零的contentInset(如在iOS 7中,当滚动视图展开到条形下方时),您需要从offsetY中减去它,如图所示。对于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.

在上面的代码之前调用[_collectionView.collectionViewLayout prepareLayout]可能会在您需要滚动尚未显示的集合视图时提供帮助(来自viewDidLoad说)。对layoutIfNeeded的调用(如注释中建议的@Vrasidas)也应该有效,因为它也准备了布局。

#2


6  

Solution in Swift,

Swift中的解决方案,

let section: = 0 // Top
if let cv = self.collectionView {

    cv.layoutIfNeeded()

    let indexPath = NSIndexPath(forItem: 1, inSection: section)
    if let attributes =  cv.layoutAttributesForSupplementaryElementOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) {

        let topOfHeader = CGPointMake(0, 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/

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

#3


1  

// 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];

#4


1  

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.

现在,您基本上将您的部分的第一行滚动到可见,减去其部分标题的高度。这将使用固定标题将节标题放在您想要的顶部,这样滚动的方向就不再重要了。我没有使用分段插入测试。

#1


37  

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

你不能使用scrollToItemAtIndexPath:atScrollPosition:为此动画。

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.

希望他们将来会添加一个像scrollToSupplementaryElementOfKind:atIndexPath:这样的新方法,但是现在,唯一的方法就是直接操作contentOffset。

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.

下面的代码显示了如何使用FlowLayout将标题滚动到垂直顶部。您可以对水平滚动执行相同操作,或将此想法用于其他布局类型。

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.

请注意,如果您有非零的contentInset(如在iOS 7中,当滚动视图展开到条形下方时),您需要从offsetY中减去它,如图所示。对于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.

在上面的代码之前调用[_collectionView.collectionViewLayout prepareLayout]可能会在您需要滚动尚未显示的集合视图时提供帮助(来自viewDidLoad说)。对layoutIfNeeded的调用(如注释中建议的@Vrasidas)也应该有效,因为它也准备了布局。

#2


6  

Solution in Swift,

Swift中的解决方案,

let section: = 0 // Top
if let cv = self.collectionView {

    cv.layoutIfNeeded()

    let indexPath = NSIndexPath(forItem: 1, inSection: section)
    if let attributes =  cv.layoutAttributesForSupplementaryElementOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) {

        let topOfHeader = CGPointMake(0, 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/

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

#3


1  

// 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];

#4


1  

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.

现在,您基本上将您的部分的第一行滚动到可见,减去其部分标题的高度。这将使用固定标题将节标题放在您想要的顶部,这样滚动的方向就不再重要了。我没有使用分段插入测试。