Uicollectionview Scrolltoitematindexpath, Not Loading Visible Cells Until Animation Complete

UICollectionview scrollToItemAtIndexPath, not loading visible cells until animation complete

The only answer I have found, is to add a few seconds to the animation. That way the cells are loaded when the scroll arrives

xcode CollectionViewController scrollToItemAtIndexPath not working

Whether it's a bug or a feature, UIKit throws this error whenever scrollToItemAtIndexPath:atScrollPosition:Animated is called before UICollectionView has laid out its subviews.

As a workaround, move your scrolling invocation to a place in the view controller lifecycle where you're sure it has already computed its layout, like so:

@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

At the very least, the error message should probably be more helpful. I've opened a rdar://13416281; please dupe.

How UICollectionView.scrollToItemAtIndexPath works?

In my opinion, even you call "scrollToItemAtIndexPath" without animation, however, when it is executed, cells between the last visible cell to the last cell will be created and rendered. Moreover, the collection view will layout subviews during that time. So that your "print" call will be executed before. I printed out "cellForItemAtIndexPath" to prove that. One more thing is if you call "print" delay after 0.5s for example, it will print the last cell.

    override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellIdentifier")
}

@IBOutlet weak var collectionView: UICollectionView!

@IBAction func didButtonPress(sender: AnyObject) {
let section = 0
let lastItemIndex = self.collectionView.numberOfItemsInSection(section) - 1
let indexPath:NSIndexPath = NSIndexPath.init(forItem: lastItemIndex, inSection: section)
self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
print(self.collectionView.indexPathsForVisibleItems())
}

}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(80, 80)
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 60
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath)
cell.backgroundColor = UIColor.redColor()
print(indexPath)
return cell
}

I hope this will help you.

Animating UICollectionView contentOffset does not display non-visible cells

You should simply add [self.view layoutIfNeeded]; inside the animation block, like so:

[UIView animateWithDuration:((self.collectionView.collectionViewLayout.collectionViewContentSize.width - self.collectionView.contentOffset.x) / 75) delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState) animations:^{
self.collectionView.contentOffset = CGPointMake(self.collectionView.collectionViewLayout.collectionViewContentSize.width, 0);
[self.view layoutIfNeeded];
} completion:nil];

UICollectionView scrollToItemAtIndex after button action executed

since you are not dismissing but popping the viewcontroller something like this should work:

override func viewWillDisappear(animated: Bool) {
if let mainViewController = navigationController?.topViewController as? MainViewController {
mainViewController.collectionView?.scrollToItemAtIndexPath(NSIndexPath(forItem: currentIndex, inSection: 0), atScrollPosition: .CenteredVertically, animated: false)
}
super.viewWillDisappear(animated)
}

Reload data calling all cells which are not visible in view

You can do the following:

tableView.beginUpdates()
tableView.reloadRows(at: tableView.indexPathsForVisibleRows ?? [IndexPath](), with: .none)
tableView.endUpdates()

Animated scroll-to-item in UICollectionView doesn't always work

I've just run in to a similar / the same issue after adding an item to a UICollectionView.

What's happening

The issue seems to be that immediately following [collectionView reloadData] or [collectionView insertItemsAtIndexPaths: @[newItemIndexPath]], the collection view's content size is not yet updated.

If you then try to scroll the added item visible, it will fail because the content rect doesn't yet include space for the new item.

A fix

There is a simple and fairly robust work around, which is to post the scroll event on to the next iteration of the run loop like this:

const NSUInteger newIndex = 
[self collectionView: self.collectionView numberOfItemsInSection: 0] - 1;

NSIndexPath *const newPath =
[NSIndexPath indexPathForItem: newIndex
inSection: 0];

[self.collectionView insertItemsAtIndexPaths: @[newPath]];

UICollectionViewLayoutAttributes *const layoutAttributes =
[self.collectionView layoutAttributesForItemAtIndexPath: newPath];

dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView scrollRectToVisible: layoutAttributes.frame
animated: YES];
});

Isn't there a nicer fix?

While it works, this "post the scroll call on the next run loop tick" shenanigans feels hacky.

It would be preferable if UICollectionView could invoke a callback when it finished updating the content rect. UIKit has callbacks of this style for other methods that perform asynchronous updates to its model. For example, a completion block for UIViewController transitions and UIView animations.

UICollectionView does not provide this callback. As far as I know, there is no other simple clean way to find when it completes its update. In the absence of this, the next run loop tick is a viable horse proxy for the callback unicorn we would prefer to use.

Anything else to know?

It's probably useful to know that UITableView also has this issue. A similar workaround should work there too.



Related Topics



Leave a reply



Submit