Как обнаружить прокрутку к новому разделу в UICollectionView?

Я реализую календарь с бесконечной прокруткой. Моя проблема заключается в том, что я хотел бы установить текущий месяц в качестве заголовка на панели навигации, и он должен обновляться при прокрутке - после того, как вы передадите представление заголовка раздела, заголовок должен обновиться на панели навигации.

Возможным решением было бы установить заголовок представления в методе, называемом - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath так что, когда я вычисляю заголовок нового раздела, он также обновляет заголовок. Проблема в том, что заголовок меняется, когда новый раздел находится внизу страницы.

В настоящее время заголовок раздела отображается как заголовок, а не как оптимальный

Есть ли способ узнать "текущий раздел" UICollectionView когда пользователь прокрутил его? Или вы можете придумать способ улучшить мое текущее решение?

Чтобы помочь читателям этого поста, я разместил свой собственный пример кода для этого вопроса в этом репозитории GitHub.

4 ответа

Решение

Изменить ниже строки в методе 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]];
}

Надеюсь, это поможет вам.

Я размышлял над алгоритмом, который позволил бы вам узнать, когда пользователь прокручивает заголовок раздела, чтобы обновить заголовок, и после некоторых экспериментов я выяснил, как реализовать желаемое поведение.

По сути, каждый раз, когда изменяется позиция прокрутки, вам нужно знать, в каком разделе находится пользователь, и обновлять заголовок. Вы делаете это через 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];
}

Это правильно обновит заголовок в навигационной панели, как только пользователь прокрутит заголовок раздела. Если они прокрутят назад, он также обновится правильно. Это также правильно устанавливает заголовок, когда они не прокручивались мимо первого раздела. Однако он не очень хорошо справляется с вращением. Это также не будет работать хорошо, если у вас есть динамический контент, что может привести к тому, что сохраненные позиции представлений заголовка будут неправильными. И если вы поддерживаете переход к определенному разделу, пользователь переходит к разделу, заголовок раздела предыдущего раздела которого еще не создан, и этот раздел недостаточно высок, чтобы заголовок раздела находился под панелью навигации (последний раздел возможно), неправильный заголовок будет отображаться в навигационной панели.

Если кто-то может улучшить это, чтобы сделать его более эффективным или иным способом, пожалуйста, сделайте, и я обновлю ответ соответственно.

Да, проблема в том, что нижний колонтитул и заголовок не существуют в коллекции visibleCells. Есть другой способ обнаружить прокрутку для верхнего / нижнего колонтитула раздела. Просто добавьте туда элемент управления и найдите прямоугольник для него. Как это:

func scrollViewDidScroll(scrollView: UIScrollView) {

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


}

Никакое решение здесь не является удовлетворительным, поэтому я придумал собственное, которым хочу полностью поделиться. Однако, если вы используете это, вам придется внести некоторые корректировки в пиксели.

      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 = self в viewDidLoad() и реализовать UIScrollViewDelegate для этого.

в scrollViewDidScroll обратного вызова мы сначала получим точку ячейки ниже, настройте cellOffsetX а также cellOffsetAheadYправильно, поэтому ваш раздел будет выбран, когда ячейка достигнет этой точки. Вы также можете изменить CGPoint чтобы получить другую точку из видимого прямоугольника, т.е. для x вы также можете использовать rect.midX / rect.maxX и любое настраиваемое смещение.

IndexPath будет возвращен из indexPathForItem(at: GCPoint) когда вы попадаете в ячейку с этими координатами.

Когда вы прокручиваете вверх, вы можете смотреть вперед, возможно, впереди вашего UICollectionReusableView верхний и нижний колонтитулы, для этого я также проверяю точку с отрицательным смещением Y, установленным в cellOffsetBehindY. Это имеет более низкий приоритет.

Итак, этот пример получит следующий раздел после того, как вы передадите заголовок, и предыдущий раздел, когда ячейка предыдущего раздела вот-вот появится в поле зрения. Вы должны настроить его в соответствии с вашими потребностями, и вы должны где-то сохранить значение и делать свое дело только при изменении текущего раздела, потому что этот обратный вызов будет вызываться в каждом кадре при прокрутке .

Другие вопросы по тегам