Logan
Logan

Reputation: 53112

UICollectionView scrollToItemAtIndexPath not functioning properly in navigation controller

I have a method that I use to scroll to the bottom of my collectionView

- (void) scrollToBottom {
    if (_messagesArray.count > 0) {
        static NSInteger section = 0;
        NSInteger item = [self collectionView:_myCollectionView numberOfItemsInSection:section] - 1;
        if (item < 0) item = 0;
        NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
        [_myCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
    }
}

And it works perfectly! I have never had a problem at any point, it works 100% consistent . . . with a modal view presentation. If however, I push the viewController onto a navigation controller, it doesn't work. Rather, it only works if there's over 15 cells. Once I reach at least 15 cells, it again begins behaving perfectly. Prior to 15 cells, it either doesn't scroll at all, or scrolls a little bit.

I realize this is a long shot, but I'm scratching my head on this one and I thought maybe somebody might know what the heck is happening.

Troubleshoots:

Have you logged to make sure it's running? YES

Have you logged index path to make sure its trying to scroll to the correct index path? YES

EXAMPLE

enter image description here

WORKING MODAL EXAMPLE

enter image description here

Upvotes: 5

Views: 7618

Answers (5)

Nikita Ivaniushchenko
Nikita Ivaniushchenko

Reputation: 1435

I found nice solution here

- (void)scrollToBottomAnimated:(BOOL)animated
{
if ([self.collectionView numberOfSections] == 0) {
    return;
}

NSInteger items = [self.collectionView numberOfItemsInSection:0];

if (items == 0) {
    return;
}

CGFloat collectionViewContentHeight = [self.collectionView.collectionViewLayout collectionViewContentSize].height;
BOOL isContentTooSmall = (collectionViewContentHeight < CGRectGetHeight(self.collectionView.bounds));

if (isContentTooSmall) {
    //  workaround for the first few messages not scrolling
    //  when the collection view content size is too small, `scrollToItemAtIndexPath:` doesn't work properly
    //  this seems to be a UIKit bug, see #256 on GitHub
    [self.collectionView scrollRectToVisible:CGRectMake(0.0, collectionViewContentHeight - 1.0f, 1.0f, 1.0f)
                                    animated:animated];
    return;
}

//  workaround for really long messages not scrolling
//  if last message is too long, use scroll position bottom for better appearance, else use top
//  possibly a UIKit bug, see #480 on GitHub
NSUInteger finalRow = MAX(0, [self.collectionView numberOfItemsInSection:0] - 1);
NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0];
CGSize finalCellSize = [self.collectionView.collectionViewLayout sizeForItemAtIndexPath:finalIndexPath];

CGFloat maxHeightForVisibleMessage = CGRectGetHeight(self.collectionView.bounds) - self.collectionView.contentInset.top - CGRectGetHeight(self.inputToolbar.bounds);

UICollectionViewScrollPosition scrollPosition = (finalCellSize.height > maxHeightForVisibleMessage) ? UICollectionViewScrollPositionBottom : UICollectionViewScrollPositionTop;

[self.collectionView scrollToItemAtIndexPath:finalIndexPath
                            atScrollPosition:scrollPosition
                                    animated:animated];
}

Upvotes: 0

Shane
Shane

Reputation: 1081

Running into the same issue in a context where I had access to the cell, I was able to use scrollRectToVisible which doesn't seem to exhibit this behavior.

Upvotes: 1

Dulgan
Dulgan

Reputation: 6694

There is a automaticallyAdjustsScrollViewInsets property in UIViewController, and as it's inherited, in UINavigationController. Set it to NO in self.navigationController and it won't adjust your insets, as documented here

Upvotes: 5

Logan
Logan

Reputation: 53112

I found the problem.

The NavigationController was automatically adjusting my top content inset for some reason. I was able to prevent this behavior by adding this:

- (void) scrollToBottom {

    if (_isNavigationControllerVersion) {
        _myCollectionView.contentInset = UIEdgeInsetsZero;
    }

    if (_messagesArray.count > 0) {
        static NSInteger section = 0;
        NSInteger item = [self collectionView:_myCollectionView numberOfItemsInSection:section] - 1;
        if (item < 0) item = 0;
        NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
        [_myCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
    }
}

Upvotes: 2

nhgrif
nhgrif

Reputation: 62052

So, since your .gif demonstrates that the problem is happening when the keyboard is open, I can only assume that the bottom scroll is within the content inset of the collection view, even if it is behind the keyboard.

Be sure you're resetting the content inset when the keyboard appears/disappears. I will edit in some example code.

First, you need to register for the notifications:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWasShown:)
                                             name:UIKeyboardDidShowNotification
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];

This should go in viewDidLoad. Don't forget to removeObserver in dealloc.

Now set the content insets in these methods:

- (void)keyboardWasShown:(NSNotification *)aNotification {
    CGSize kbSize = [[[aNotification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    UIEdgeInsets insets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    yourCollectionView.contentInset = Insets;
    yourCollectionView.scrollIndicatorInsets = Insets;
}

- (void) keyboardWillHide:(NSNotification *)aNotification {
    UIEdgeInsets insets = UIEdgeInsetsZero;
    yourCollectionView.contentInset = contentInsets;
    yourCollectionView.scrollIndicatorInsets = contentInsets;
}

Upvotes: 1

Related Questions