Автоматическая прокрутка UICollectionView к ячейке в IndexPath

101

Перед загрузкой представления коллекции пользователь устанавливает номер изображения в массиве представления коллекции. Все ячейки не помещаются на экране. У меня 30 ячеек и только 6 на экране.

Вопрос: как при загрузке UICollectionView автоматически перейти к ячейке с нужным изображением?

AlexanderZ
источник

Ответы:

90

Я обнаружил, что прокрутка viewWillAppearможет работать ненадежно, потому что представление коллекции еще не завершило свою компоновку; вы можете перейти к неправильному элементу.

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

И, если вы прокручиваете каждый раз viewDidLayoutSubviews, пользователь не сможет прокручивать вручную, потому что некоторые макеты коллекций вызывают макет subview каждый раз, когда вы прокручиваете.

Вот то, что я обнаружил, надежно работает:

Цель C:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // If we haven't done the initial scroll, do it once.
    if (!self.initialScrollDone) {
        self.initialScrollDone = YES;

        [self.collectionView scrollToItemAtIndexPath:self.myInitialIndexPath
                                    atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
    }
}

Swift:

 override func viewWillLayoutSubviews() {

    super.viewWillLayoutSubviews()

      if (!self.initialScrollDone) {

         self.initialScrollDone = true
         self.testNameCollectionView.scrollToItem(at:selectedIndexPath, at: .centeredHorizontally, animated: true)
    }

}
LenK
источник
Хороший совет. Я подчеркиваю использование initialScrollDoneфлага, как это сделал LenK, поскольку этот метод будет вызываться более одного раза, и если вы вызовете scrollToItemAtIndexPath: более одного раза, кажется, что collectionView не прокручивается (симулятор iOS iPhone 5 / iOS 8)
maz
1
Отлично работает в iOS8. Должен быть принятый ответ.
Ник
5
У меня это не сработало в iOS 8.3. Пришлось звонить [self.collectionView layoutIfNeeded];раньше, что также работает, когда вы делаете это внутри viewDidLoad.
Брент Диллингем,
1
Это определенно должен быть принятый ответ. Приведенный выше ответ дал мне вспышку, поскольку первая ячейка была показана, а затем обновлена ​​моей новой ячейкой, этот метод плавно переходит.
simon_smiley
это также работает на iOS 9 и не портит мой автоматический макет.
Мистер Zystem
168

Новый, отредактированный ответ:

Добавьте это в viewDidLayoutSubviews

SWIFT

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let indexPath = IndexPath(item: 12, section: 0)
    self.collectionView.scrollToItem(at: indexPath, at: [.centeredVertically, .centeredHorizontally], animated: true)
}

Обычно .centerVerticallyэто так

ObjC

-(void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:12 inSection:0];
   [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically | UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}

Старый ответ работает для более старой iOS

Добавьте это в viewWillAppear:

[self.view layoutIfNeeded];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
Boweidmann
источник
4
Это именно то, что я пытаюсь использовать. У меня есть номер объекта в массиве, и я знаю имя этого объекта. Но не знаю, как правильно указать indexPath ... Есть идеи?
AlexanderZ
4
Вы можете легко создать indexPath:[NSIndexPath indexPathForItem:numberOfTheObject inSection:sectionNumber]
Boweidmann
3
Были ошибки. Но затем я поместил весь упомянутый код в (void) viewDidLayoutSubviews {}, и теперь он работает нормально. Спасибо Богдан.
AlexanderZ
1
Как я могу «увеличить» ячейку и показать ее в увеличенном виде?
fatuhoku
9
Это не работает надежно в iOS 7.1 и может привести к тому, что представление коллекции будет отображаться как черный экран. Я также не хотел поддерживать состояние, подобное решению в viewDidLayoutSubviews ниже. Я просто выполнил [self.view layoutIfNeeded] прямо перед вызовом scrollToItemAtIndexPath
Даниэль Галаско
16

Я полностью согласен с приведенным выше ответом. Единственное, что для меня решением был установлен код в viewDidAppear

viewDidAppear 
{
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}

Когда я использовал viewWillAppear, прокрутка не работала.

Рейстлин
источник
2
У viewDidAppear есть недостаток, заключающийся в том, что вы видите перемещение / мигание представления коллекции, представление уже появилось (как следует из названия) ... Мне действительно интересно, почему Apple не разработала этот не такой необычный случай более приятным способом, способ viewDidLayoutSubviews - это немного неловко
TheEye
12

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

- (void)viewDidLayoutSubviews
{     
   [self.collectionView layoutIfNeeded];
   NSArray *visibleItems = [self.collectionView indexPathsForVisibleItems];
   NSIndexPath *currentItem = [visibleItems objectAtIndex:0];
   NSIndexPath *nextItem = [NSIndexPath indexPathForItem:someInt inSection:currentItem.section];

   [self.collectionView scrollToItemAtIndexPath:nextItem atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
}
теплица
источник
12

Swift:

your_CollectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)

Swift 3

let indexPath = IndexPath(row: itemIndex, section: sectionIndex)

collectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.right, animated: true)

Положение прокрутки:

UICollectionViewScrollPosition.CenteredHorizontally / UICollectionViewScrollPosition.CenteredVertically
Раджеш Логанатан
источник
9

Вы можете использовать GCD для отправки прокрутки в следующую итерацию основного цикла выполнения в viewDidLoad для достижения такого поведения. Прокрутка будет выполнена до того, как представление коллекции отобразится на экране, поэтому мигания не будет.

- (void)viewDidLoad {
    dispatch_async (dispatch_get_main_queue (), ^{
        NSIndexPath *indexPath = YOUR_DESIRED_INDEXPATH;
        [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    });
}
Тяньюй Ван
источник
как получить indexpath, когда позиция 3?
kemdo
5

Я бы порекомендовал сделать это в. collectionView: willDisplay:Тогда вы можете быть уверены, что делегат представления коллекции что-то доставляет. Вот мой пример:

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    /// this is done to set the index on start to 1 to have at index 0 the last image to have a infinity loop
    if !didScrollToSecondIndex {
        self.scrollToItem(at: IndexPath(row: 1, section: 0), at: .left, animated: false)
        didScrollToSecondIndex = true
    }
}
Эйке
источник
1
Я обнаружил, что использование didEndDisplaying вместо willDisplay было способом заставить это работать должным образом. willDisplay был в порядке, пока ячейка была на экране; однако, если ячейка еще не была на экране, этот метод не прокручивал меня до нее. didEndDisplaying отлично работал для этого варианта использования. Ценю ваш ответ, направивший меня на правильный путь!
C6Silver
2

В качестве альтернативы упомянутому выше. Звонок после загрузки данных:

Swift

collectionView.reloadData()
collectionView.layoutIfNeeded()
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .right)
nCod3d
источник
1

Все ответы здесь - хаки. Я нашел лучший способ прокручивать представление коллекции где-нибудь после relaodData:
макет представления коллекции подклассов, какой бы макет вы ни использовали, переопределите метод prepareLayout, после супер-вызова установите то, что вам нужно.
пример: https://stackoverflow.com/a/34192787/1400119

обманщик77777
источник