Scroll to item in collection view crashes the app
ios, objective-c, uicollectionview
Solution
I had the same problem and could solve it. First of all, when you create the UICollectionView you must specify a frame with its width, no matters the height, but the width it's very important to scroll to the correct item.
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_];
After creating the UICollectionView you must tell the view that needs update its constraints, because in iOS6 you have to force it, so invoke updateViewConstraints:
[self updateViewConstraints]
Override the method updateViewConstraints, and set here all the view constraints. Remember to remove all the constraints of the view before invoke super (in your code you are not removing them), and set on metrics' dictionary the width of the UICollectionView and don't use [collectionView_(==scrollView_)] because sometimes it fails, mainly in 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]];
}
Finally, to scroll the UICollectionView to the correct item, do it on viewWillLayoutSubviews and don't forget to check if UICollectionView's size is not zero to avoid app crash:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (!CGSizeEqualToSize([collectionView_ frame].size, CGSizeZero)) {
[collectionView_ scrollToItemAtIndexPath:_selectedRowItem_ inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
}
}
That's all. Hope it helps!
Problem
I want to scroll to a certain item of an `UICollectionView` inside `viewWillAppear` ``` - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [collectionView_ scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO]; } ``` On iOS 6 this code crashes the app returning ``` *** 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]' ``` On iOS7 it does not crashes but is simply does nothing. The scrolling to the correct item only works in `viewDidAppear` but I want to show the screen with the collection in the correct item, on appear. I tried to scroll it in `viewDidLayoutSubviews` but it also crashes. Wrapping the call inside a `try-catch` avoids the crash but it still does not working. What is the point of this? Is it impossible to show the correct item on appear? Thank you so much. EDIT 1 I printed this on `viewWillAppear` and `viewDidLayoutSubviews` (`selectedIndex_` is 2, and the collection has 10 items): ``` UICollectionViewLayoutAttributes *test = [collectionView_ layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0]]; ``` The result is this in both places. ``` <UICollectionViewLayoutAttributes: 0x11b9ff20> index path: (<NSIndexPath: 0x11b9c450> {length = 2, path = 0 - 2}); frame = (0 0; 0 0); ``` EDIT 2 This is the trace I print of the `contentSize` of the collection ``` 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} ``` The collection view is created programatically in `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_` is created via XIB (the only control in the XIB. I need another scroll to put some other control below the horizontal collection). The constraints of this method are set in `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` creates all its controls in its `initWithFrame` method, and here is the method to return the cell. ``` - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MyCollectionViewCell collectionCellIdentifier] forIndexPath:indexPath]; // Data filling return cell; } ```