8

我正在实现一个无限滚动的日历。我的问题是我想将当前月份设置为导航栏中的标题,并且它应该在滚动时更新 - 一旦您通过部分标题视图,标题应该在导航栏中更新。

一个可能的解决方案是在调用的方法中设置视图标题,- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath这样,当我计算一个新的部分标题时,它也会更新标题。这样做的问题是,当新部分位于页面底部时,标题会发生变化。

当前将节标题显示为标题,不是最佳的

UICollectionView一旦用户滚动到它,有没有办法知道“当前部分” ?或者你能想出一种方法来改进我目前的解决方案吗?

为了帮助这篇文章的读者,我在这个GitHub repo 上发布了我自己的这个问题的示例代码。

4

4 回答 4

2

我一直在思考一种算法,它可以让您知道用户何时滚动过节标题以更新标题,并且经过一些实验,我已经弄清楚如何实现所需的行为。

本质上,每次滚动位置发生变化时,您都需要知道用户在哪个部分并更新标题。您可以通过- 记住集合视图是滚动视图来执行此scrollViewDidScroll操作。UIScrollViewDelegate遍历所有标题并找到最接近当前滚动位置的标题,而没有负偏移量。为此,我使用了一个属性来存储每个节标题位置的数组。创建标头时,我将其位置存储在数组中适当的索引处。找到最接近滚动位置(或所述标题的索引位置)的标题后,只需使用适当的标题更新导航栏中的标题。

在中,为您拥有的每个部分viewDidLoad填充数组属性:NSNull

self.sectionHeaderPositions = [[NSMutableArray alloc] init];
for (int x = 0; x < self.sectionTitles.count; x++) {
    [self.sectionHeaderPositions addObject:[NSNull null]];
}

collectionView:viewForSupplementaryElementOfKind:atIndexPath:中,使用创建的标题视图的位置更新数组:

NSNumber *position = [NSNumber numberWithFloat:headerView.frame.origin.y + headerView.frame.size.height];
[self.sectionHeaderPositions replaceObjectAtIndex:indexPath.section withObject:position];

scrollViewDidScroll:中,执行计算以确定哪个标题适合显示该滚动位置:

CGFloat currentScrollPosition = self.collectionView.contentOffset.y + self.collectionView.contentInset.top;
CGFloat smallestPositiveHeaderDifference = CGFLOAT_MAX;
int indexOfClosestHeader = NSNotFound;

//find the closest header to current scroll position (excluding headers that haven't been reached yet)
int index = 0;
for (NSNumber *position in self.sectionHeaderPositions) {
    if (![position isEqual:[NSNull null]]) {
        CGFloat floatPosition = position.floatValue;
        CGFloat differenceBetweenScrollPositionAndHeaderPosition = currentScrollPosition - floatPosition;
        if (differenceBetweenScrollPositionAndHeaderPosition >= 0 && differenceBetweenScrollPositionAndHeaderPosition <= smallestPositiveHeaderDifference) {
            smallestPositiveHeaderDifference = differenceBetweenScrollPositionAndHeaderPosition;
            indexOfClosestHeader = index;
        }
    }
    index++;
}
if (indexOfClosestHeader != NSNotFound) {
    self.currentTitle.text = self.sectionTitles[indexOfClosestHeader];
} else {
    self.currentTitle.text = self.sectionTitles[0];
}

一旦用户滚动到某个部分的标题,这将正确更新导航栏中的标题。如果他们向上滚动,它也会正确更新。当他们没有滚动到第一部分时,它也会正确设置标题。然而,它不能很好地处理旋转。如果您有动态内容,它也不会很好地工作,这可能会导致标题视图的存储位置不正确。如果您支持跳转到特定部分,则用户会跳转到上一个部分的部分标题尚未创建的部分,并且该部分不够高,以至于部分标题位于导航栏下方(最后一个部分也许),不正确的标题将显示在导航栏中。

如果有人可以对此进行改进以使其更有效或更好,请这样做,我会相应地更新答案。

于 2015-02-17T04:16:38.790 回答
0

在方法中更改以下行viewForSupplementaryElementOfKind

self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section]];

对此:

    if(![[self dateForFirstDayInSection:indexPath.section-1] isKindOfClass:[NSNull class]]){
        self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section-1]];
}

希望它会帮助你。

于 2015-02-16T09:44:57.870 回答
0

是的,问题是页脚和页眉在 visibleCells 集合中不存在。还有其他方法可以检测部分页眉/页脚的滚动。只需在那里添加一个控件并找到它的矩形。像这样:

func scrollViewDidScroll(scrollView: UIScrollView) {

    if(footerButton.tag == 301)
    {
        let frame : CGRect = footerButton.convertRect(footerButton.frame, fromView: self.view)
        //some process for frame
    }


}
于 2015-02-16T10:00:33.777 回答
0

这里没有解决方案是令人满意的,所以我想出了我自己想要完全分享的解决方案。但是,如果您使用它,则必须进行一些像素调整。

extension MyViewControllerVC: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView == self.myCollectionView {
            let rect = CGRect(origin: self.myCollectionView.contentOffset, size: self.cvProductItems.bounds.size)
            let cellOffsetX: CGFloat = 35 // adjust this
            let cellOffsetAheadY: CGFloat = 45 // adjust this
            let cellOffsetBehindY: CGFloat = 30 // adjust this
            var point: CGPoint = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY + cellOffsetAheadY) // position of cell that is ahead

            var indexPath = self.myCollectionView.indexPathForItem(at: point)
            if indexPath?.section != nil { // reached next section
                // do something with your section (indexPath!.section)
            } else {
                point = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY - cellOffsetBehindY) // position of cell that is behind
                indexPath = self.myCollectionView.indexPathForItem(at: point)
                if indexPath?.section != nil { // reached previous section
                        // do something with your section (indexPath!.section)
                }
            }
        }
    }

}

UICollectionView继承UIScrollView,所以我们可以只做self.myCollectionView.delegate = selfinviewDidLoad()并实现UIScrollViewDelegate它。

scrollViewDidScroll回调中,我们将首先获取下面一个单元格的点,cellOffsetX并进行cellOffsetAheadY适当的调整,这样当单元格到达该点时,您的部分将被选中。您还可以修改CGPoint以获得与可见矩形不同的点,即对于 x 您还可以使用rect.midX/rect.maxX和任何自定义偏移量。

indexPathForItem(at: GCPoint)当您点击具有这些坐标的单元格时,将返回一个 indexPath 。

当您向上滚动时,您可能想要向前看,可能是在您的UICollectionReusableView页眉和页脚前面,为此我还检查了在 中设置了负 Y 偏移的点cellOffsetBehindY。这具有较低的优先级。

所以,一旦你传递了标题,这个例子就会得到下一节,一旦上一节的单元格即将进入视图,就会得到上一节。您必须对其进行调整以满足您的需要,并且您应该将值存储在某处,并且仅在当前部分更改时才执行您的操作,因为此回调将在滚动时在每一帧上调用

于 2021-05-02T22:05:44.043 回答