Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll to item in collection view crashes the app

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;   
}
like image 680
emenegro Avatar asked Dec 03 '13 15:12

emenegro


1 Answers

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!

like image 122
iPili85 Avatar answered Nov 15 '22 20:11

iPili85