Xcode CollectionViewController scrollToItemAtIndexPath не работает
Я создал элемент управления CollectionView
и заполнил его изображениями. Теперь я хочу прокручивать элемент по определенному индексу при запуске. Я опробовал scrollToItemAtIndexPath
следующим образом:
[self.myFullScreenCollectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
Однако я получаю следующее исключение. Может ли кто-нибудь направить меня туда, где я ошибаюсь.
2013-02-20 02:32:45.219 ControlViewCollection1[1727:c07] *** Assertion failure in
-[UICollectionViewData layoutAttributesForItemAtIndexPath:], /SourceCache/UIKit_Sim/UIKit-2380.17
/UICollectionViewData.m:485 2013-02-20 02:32:45.221 ControlViewCollection1[1727:c07] must return a
UICollectionViewLayoutAttributes instance from -layoutAttributesForItemAtIndexPath: for path
<NSIndexPath 0x800abe0> 2 indexes [0, 4]
Ответы
Ответ 1
Является ли это ошибкой или функцией, UIKit выдает эту ошибку всякий раз, когда вызывается scrollToItemAtIndexPath:atScrollPosition:Animated
до того, как UICollectionView
выложил ее в виде subviews.
Как обходной путь, переместите свой вызов прокрутки в место в жизненном цикле контроллера представления, где вы уверены, что он уже вычислил его макет, например:
@implementation CollectionViewControllerSubclass
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// scrolling here doesn't work (results in your assertion failure)
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSIndexPath *indexPath = // compute some index path
// scrolling here does work
[self.collectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}
@end
По крайней мере, сообщение об ошибке должно быть более полезным. Я открыл rdar://13416281; прошу прощения.
Ответ 2
Если вы пытаетесь выполнить прокрутку, когда контроллер просмотра загружается, обязательно вызовите layoutIfNeeded
в UICollectionView
, прежде чем вы вызовете scrollToItemAtIndexPath
. Это лучше, чем перенос логики прокрутки в viewDidLayoutSubviews, потому что вы не будете выполнять операцию прокрутки каждый раз, когда выкладываются представления в родительском представлении.
Ответ 3
U может сделать это и по методу viewDidLoad
просто сделайте вызов preformBatchUpdates
[self performBatchUpdates:^{
if ([self.segmentedDelegate respondsToSelector:@selector(segmentedBar:selectedIndex:)]){
[self.segmentedDelegate segmentedBar:self selectedIndex:_selectedPage];
}
} completion:^(BOOL finished) {
if (finished){
[self scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:_selectedPage inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
}];
В моем случае у моего подкласса CollectionView есть свойство selectedPage, а на установщике этого свойства я вызываю
- (void)setSelectedPage:(NSInteger)selectedPage {
_selectedPage = selectedPage;
[self performBatchUpdates:^{
if ([self.segmentedDelegate respondsToSelector:@selector(segmentedBar:selectedIndex:)]){
[self.segmentedDelegate segmentedBar:self selectedIndex:_selectedPage];
}
} completion:^(BOOL finished) {
if (finished){
[self scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:_selectedPage inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
}];
}
В контроллере, который я вызываю это кодом
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.segmentedPages.selectedPage = 1;
}
Ответ 4
Добавление логики прокрутки к viewDidAppear
работало для меня:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.collectionView?.scrollToItemAtIndexPath(
someIndexPath,
atScrollPosition: UICollectionViewScrollPosition.None,
animated: animated)
}
Добавление его в viewDidLoad
не работает: оно игнорируется.
Добавление его в viewDidLayoutSubviews
не работает, если вы не хотите прокручивать логику, вызывая любое изменение. В моем случае он не позволял пользователю вручную прокручивать элемент
Ответ 5
Также помните, что вам нужно использовать правильное значение UICollectionviewScrollPosition. Пожалуйста, ознакомьтесь с кодом ниже:
typedef NS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) {
UICollectionViewScrollPositionNone = 0,
/* For a view with vertical scrolling */
// The vertical positions are mutually exclusive to each other, but are bitwise or-able with the horizontal scroll positions.
// Combining positions from the same grouping (horizontal or vertical) will result in an NSInvalidArgumentException.
UICollectionViewScrollPositionTop = 1 << 0,
UICollectionViewScrollPositionCenteredVertically = 1 << 1,
UICollectionViewScrollPositionBottom = 1 << 2,
/* For a view with horizontal scrolling */
// Likewise, the horizontal positions are mutually exclusive to each other.
UICollectionViewScrollPositionLeft = 1 << 3,
UICollectionViewScrollPositionCenteredHorizontally = 1 << 4,
UICollectionViewScrollPositionRight = 1 << 5
};
Ответ 6
Начиная с iOS 9.3, Xcode 8.2.1, Swift 3:
Вызов scrollToItem(at:)
из viewWillAppear()
по-прежнему прерывается, особенно если вы используете верхние и нижние колонтитулы разделов, автоматический макет, вставки разделов.
Даже если вы вызываете setNeedsLayout()
и layoutIfNeeded()
в collectionView
, поведение по-прежнему встречается. Включение кода прокрутки в блок анимации не работает надежно.
Как указано в других ответах, решение состоит в том, чтобы вызвать scrollToItem(at:)
только после того, как вы уверены, что все было выложено. т.е. в viewDidLayoutSubviews()
.
Однако вам нужно быть избирательным; вы не хотите выполнять прокрутку каждый раз, когда вызывается viewWillLayoutSubviews()
. Таким образом, решение состоит в том, чтобы установить флаг в viewWillAppear()
и действовать на нем в viewDidLayoutSubviews()
.
то есть.
fileprivate var needsDelayedScrolling = false
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.needsDelayedScrolling = true
// ...
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if self.needsDelayedScrolling {
self.needsDelayedScrolling = false
self.collectionView!.scrollToItem(at: someIndexPath,
at: .centeredVertically,
animated: false)
}
}
}
Ответ 7
Swift 3
UIView.animate(withDuration: 0.4, animations: {
myCollectionview.scrollToItem(at: myIndexPath, at: .centeredHorizontally, animated: false)
}, completion: {
(value: Bool) in
// put your completion stuff here
})
Ответ 8
Иногда collectionView(_:didSelectItemAt:)
либо не вызывается в основном потоке, либо блокирует его, вызывая scrollToItem(at:at:animated:)
ничего не делать.
Обходите это, делая:
DispatchQueue.main.async {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
Ответ 9
Это основано на ответе @Womble, все кредиты идут им:
Метод viewDidLayoutSubviews()
вызывается повторно. Для меня (iOS 11.2) при первом вызове collectionView.contentSize
есть {0,0}
. Во второй раз contentSize
верен. Поэтому мне пришлось добавить чек для этого:
var needsDelayedScrolling = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.needsDelayedScrolling = true
// ...
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.needsDelayedScrolling && collectionView.contentSize.width > 0 {
self.needsDelayedScrolling = false
self.collectionView!.scrollToItem(at: someIndexPath,
at: .centeredVertically,
animated: false)
}
}
}
После добавления этого дополнительного && collectionView.contentSize.width > 0
он работает красиво.