Прокрутка к элементу в представлении коллекции приводит к сбою приложения
Я хочу прокрутить до определенного элемента UICollectionView
внутри viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[collectionView_ scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
}
В iOS 6 этот код сбрасывает приложение, возвращающее
*** Assertion failure in -[UICollectionViewData layoutAttributesForItemAtIndexPath:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:485
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'must return a UICollectionViewLayoutAttributes instance from -layoutAttributesForItemAtIndexPath: for path <NSIndexPath 0x13894e70> 2 indexes [0, 2]'
На iOS7 он не сработает, а просто ничего не делает.
Прокрутка к правильному элементу работает только в viewDidAppear
, но я хочу показать экран с коллекцией в соответствующем элементе. Я попытался прокрутить его в viewDidLayoutSubviews
, но он также сработает. Обертка вызова внутри try-catch
позволяет избежать сбоя, но он все еще не работает.
В чем смысл этого? Невозможно отобразить правильный элемент?
Большое вам спасибо.
РЕДАКТИРОВАТЬ 1
Я напечатал это на viewWillAppear
и viewDidLayoutSubviews
(selectedIndex_
равно 2, а коллекция имеет 10 элементов):
UICollectionViewLayoutAttributes *test = [collectionView_ layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0]];
Результат получается в обоих местах.
<UICollectionViewLayoutAttributes: 0x11b9ff20> index path: (<NSIndexPath: 0x11b9c450> {length = 2, path = 0 - 2}); frame = (0 0; 0 0);
РЕДАКТИРОВАТЬ 2
Это трассировка, которую я печатаю contentSize
коллекции
2013-12-09 08:56:59.300 - didLoad {0, 0}
2013-12-09 08:56:59.315 - willAppear {0, 0}
2013-12-09 08:56:59.350 - viewDidLayoutSubviews {0, 0}
2013-12-09 08:56:59.781 - viewDidLayoutSubviews {3200, 223}
2013-12-09 08:56:59.879 - didAppear {3200, 223}
2013-12-09 08:56:59.882 - viewDidLayoutSubviews {3200, 223}
Вид коллекции создается программно в viewDidLoad
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
collectionView_ = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[collectionView_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[collectionView_ setDelegate:self];
[collectionView_ setDataSource:self];
[collectionView_ setShowsHorizontalScrollIndicator:NO];
[collectionView_ setPagingEnabled:YES];
[collectionView_ setBackgroundColor:[UIColor whiteColor]];
[collectionView_ registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:[MyCollectionViewCell collectionCellIdentifier]];
[scrollView_ addSubview:collectionView_];
scrollView_
создается с помощью XIB (единственного элемента управления в XIB. Мне нужен еще один свиток, чтобы поместить некоторый другой элемент управления под горизонтальную коллекцию). Ограничения этого метода устанавливаются в updateViewConstraints
- (void)updateViewConstraints {
[super updateViewConstraints];
NSDictionary *views = [self viewsDictionary];
NSDictionary *metrics = @{ @"bigMargin" : @12, @"collectionViewHeight" : @(collectionViewHeight_) };
NSMutableString *verticalConstraints = [NSMutableString stringWithString:@"V:|[collectionView_(==collectionViewHeight)]"];
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView_(==scrollView_)]|"
options:0
metrics:nil
views:views]];
if (extendedInformationView_) {
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[extendedInformationView_(==scrollView_)]|"
options:0
metrics:nil
views:views]];
[verticalConstraints appendFormat:@"-bigMargin-[extendedInformationView_]"];
}
if (actionListView_) {
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[actionListView_(==scrollView_)]|"
options:0
metrics:nil
views:views]];
[verticalConstraints appendFormat:@"-bigMargin-[actionListView_]"];
}
[verticalConstraints appendString:@"-bigMargin-|"];
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints
options:0
metrics:metrics
views:views]];
}
MyCollectionViewCell
создает все свои элементы управления в своем методе initWithFrame
, и вот метод для возврата ячейки.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MyCollectionViewCell collectionCellIdentifier]
forIndexPath:indexPath];
// Data filling
return cell;
}
Ответы
Ответ 1
У меня была такая же проблема, и я мог ее решить. Прежде всего, когда вы создаете UICollectionView, вы должны указать кадр с его шириной, не имеющий значения для высоты, но для ширины очень важно прокрутить до нужного элемента.
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
collectionView_ = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, CGRectGetWidth([scrollView_ frame]), 0.0f)
collectionViewLayout:layout];
[collectionView_ setDelegate:self];
[collectionView_ setDataSource:self];
[collectionView_ setBackgroundColor:[UIColor clearColor]];
[collectionView_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[collectionView_ setShowsHorizontalScrollIndicator:NO];
[collectionView_ setPagingEnabled:YES];
[scrollView_ addSubview:collectionView_];
После создания UICollectionView вы должны указать представление, которое нуждается в обновлении его ограничений, потому что в iOS6 вы должны принудительно его активировать, поэтому вызовите updateViewConstraints:
[self updateViewConstraints]
Отмените метод updateViewConstraints и установите здесь все ограничения вида. Не забудьте удалить все ограничения представления перед вызовом super (в коде, который вы не удаляете), и установите в словаре показателей ширину UICollectionView и не используйте [collectionView _ (= = scrollView_)], потому что иногда он терпит неудачу, главным образом в iOS6.
- (void)updateViewConstraints {
[scrollView_ removeConstraints:[scrollView_ constraints]];
[super updateViewConstraints];
NSDictionary *views = [self viewsDictionary];
NSDictionary *metrics = @{ @"bigMargin" : @12, @"collectionViewHeight" : @(collectionViewHeight_), @"viewWidth" : @(CGRectGetWidth([scrollView_ frame]) };
NSMutableString *verticalConstraints = [NSMutableString stringWithString:@"V:|[collectionView_(==collectionViewHeight)]"];
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView_(==viewWidth)]|"
options:0
metrics:nil
views:views]];
if (extendedInformationView_) {
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[extendedInformationView_(==scrollView_)]|"
options:0
metrics:nil
views:views]];
[verticalConstraints appendFormat:@"-bigMargin-[extendedInformationView_]"];
}
if (actionListView_) {
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[actionListView_(==scrollView_)]|"
options:0
metrics:nil
views:views]];
[verticalConstraints appendFormat:@"-bigMargin-[actionListView_]"];
}
[verticalConstraints appendString:@"-bigMargin-|"];
[scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints
options:0
metrics:metrics
views:views]];
}
Наконец, для прокрутки UICollectionView к правильному пункту, сделать это viewWillLayoutSubviews и не забудьте проверить, если размер UICollectionView является не равно нулю, чтобы избежать аварии приложения:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (!CGSizeEqualToSize([collectionView_ frame].size, CGSizeZero)) {
[collectionView_ scrollToItemAtIndexPath:_selectedRowItem_ inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
}
}
Это все. Надеюсь, это поможет!
Ответ 2
Кадр вашего представления коллекции первоначально представляет собой CGRectZero, для того, чтобы макет потока работал, он должен иметь представление коллекции с фреймом. Это происходит, когда вы обновляете ограничения макета, которые слишком поздно в жизненном цикле просмотра.
collectionView_ = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
Надеюсь, что это поможет.
Ответ 3
Я смог воспроизвести вашу проблему.
Проблема в том, что UICollectionView
не знает о его размере содержимого в момент прокрутки до указанного NSIndexPath
.
Вот код для воспроизведения проблемы:
@interface TLCollectionViewController : UIViewController
@end
@interface CollectionCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *titleLbl;
@end
@implementation CollectionCell
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_titleLbl = [[UILabel alloc] init];
[_titleLbl setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:_titleLbl];
NSArray *titleLblHConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[titleLbl]|" options:kNilOptions metrics:nil views:@{ @"titleLbl" : _titleLbl }];
NSArray *titleLblVConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[titleLbl]|" options:kNilOptions metrics:nil views:@{ @"titleLbl" : _titleLbl }];
[[self contentView] addConstraints:titleLblHConstrArr];
[[self contentView] addConstraints:titleLblVConstrArr];
[self setBackgroundColor:[UIColor whiteColor]];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.titleLbl.text = @"";
}
@end
@interface TLCollectionViewController () <UICollectionViewDataSource>
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) UICollectionView *collView;
@end
@implementation TLCollectionViewController
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
[self.view setBackgroundColor:[UIColor whiteColor]];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.items = @[ @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
, @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three" ];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
self.collView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[self.collView setDataSource:self];
[self.collView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.collView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"collCell"];
[self.view addSubview:self.collView];
NSArray *collViewHConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collView(==300)]" options:kNilOptions metrics:nil views:@{ @"collView" : self.collView }];
NSArray *collViewVConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[collView(==300)]" options:kNilOptions metrics:nil views:@{ @"collView" : self.collView }];
[self.view addConstraints:collViewHConstrArr];
[self.view addConstraints:collViewVConstrArr];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// BUG: here on iOS 6 exception is raised, because UICollectionView doesn't know about it content size and about it frame
// but on iOS 7 it does know about it frame, thus makes it possible to know about attributes
id attr = [self.collView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]];
[self.collView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"collCell" forIndexPath:indexPath];
cell.titleLbl.text = self.items[indexPath.row];
return cell;
}
@end
Это поведение отличается от iOS 6 и iOS 7.
В iOS 6, если вы пытаетесь получить атрибуты с UICollectionView
, который не имеет размер содержимого или, вы получите исключение NSInternalInconsistencyException
. Что касается iOS 7, это как-то изменилось, и теперь вы не должны знать ни о размере контента UICollectionView
, ни о нем, чтобы получить атрибуты для определенного NSIndexPath
.
Относительно вызова -[UICollectionViewData layoutAttributesForItemAtIndexPath:]
- этот метод вызывается автоматически при попытке выполнить любую прокрутку UICollectionView
.
![enter image description here]()
Чтобы ответить на ваш вопрос:
Невозможно отобразить правильный элемент?
Да, это невозможно сделать. Вы должны знать макет, чтобы иметь возможность прокручивать его правильно. Правильный способ выполнения прокрутки - реализовать его в -[UIViewController viewDidLayoutSubviews]
:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.collView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
}
Ответ 4
Кажется, что UIKit сработает, когда -scrollToItemAtIndexPath: atScrollPosition: Animated вызывается, когда UICollectionView еще не выложен, как вы могли видеть на Radar
Таким образом, вы можете поместить его только в viewDidAppear и и viewDidLayoutSubviews на iOS7 и только поместить его в viewDidAppear на iOS6. почему даже viewDidLayoutSubviews исключается в iOS6, отображается в вашем журнале:
2013-12-09 08:56:59.300 - didLoad {0, 0}
2013-12-09 08:56:59.315 - willAppear {0, 0}
2013-12-09 08:56:59.350 - viewDidLayoutSubviews {0, 0}
2013-12-09 08:56:59.781 - viewDidLayoutSubviews {3200, 223}
2013-12-09 08:56:59.879 - didAppear {3200, 223}
2013-12-09 08:56:59.882 - viewDidLayoutSubviews {3200, 223}
Когда вызывается первый раз viewDidLayoutSubviews, UICollectionView еще не выложен. iOS7 работает во второй раз viewDidLayoutSubviews, но iOS 6 будет аварийно завершен в первый раз.
Ответ 5
Здесь случайный дополнительный удержание будет работать в прежние дни. У меня было несколько раз, когда память освобождается и перераспределяется, поэтому приложение ищет что-то по адресу, который был переработан, что приводит к внутренней ошибке несогласованности.
Вероятно, гораздо лучший способ добавить сильный (вероятно, __strong), но я стараюсь сохранять такие свойства как свойства, а также:
@property (nonatomic,strong) UICollectionView *collectionView_;
Будет содержать сильную ссылку на данные, которые, надеюсь, остановят несостоятельность.
Martin
Ответ 6
Это может быть связано.
[NSIndexPath indexPathForRow: NSNotFound inSection: index]
Используйте NSNotFound вместо 0.
Ответ 7
Убедитесь, что содержимое коллекционного просмотра загружено в viewWillAppear. CollectionView/TableView загрузит данные, когда на экране появится представление. Попробуйте прокрутить элемент в режиме viewDidAppear: или используйте некоторую задержку.