ios lazy Creating slow scrolling to indexPath in UICollectionView

uitableview cell changes value when scrolling swift (4)

In .h file, declare this timer,

NSTimer *Timer;

Place this timer code,

  [CollectionView reloadData];
  Timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(AutoScroll) userInfo:nil repeats:YES];


#pragma mark- Auto scroll image collectionview
    if (i<[Array count])
        NSIndexPath *IndexPath = [NSIndexPath indexPathForRow:i inSection:0];
        [self.ImageCollectionView scrollToItemAtIndexPath:IndexPath
            atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
        if (i==[Array count])

Hope it'll be useful.

I'm working on a project where I'm using a UICollectionView to create an 'image ticker' where I'm advertising a series of logos. The collectionView is one item high and twelve items long, and shows two to three items at a time (depending on size of the logos visible).

I would like to make a slow automatic scrolling animation from the first item to the last, and then repeat.

Has anyone been able to make this work? I can get the scrolling working using

[myCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(myImages.count -1) inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:YES];

But this is way too fast!

[UIView animateWithDuration:10 delay:2 options:(UIViewAnimationOptionAutoreverse + UIViewAnimationOptionRepeat) animations:^{
    [myCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(myImages.count -1) inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
} completion:nil];

This yields the desired scrolling speed, but only the last few cells are visible in the series. I suspect they (and even the starting visible cells) are being dequeued immediately.

Any thoughts?

Answer #1

You could also have a "slow" scroll to the end of you UICollectionView without having to "jump" from indexpath to indexpath. I created this quickly for a collection view on a TVOS app:

func autoScroll () {
    let co = collectionView.contentOffset.x
    let no = co + 1

    UIView.animateWithDuration(0.001, delay: 0, options: .CurveEaseInOut, animations: { [weak self]() -> Void in
        self?.collectionView.contentOffset = CGPoint(x: no, y: 0)
        }) { [weak self](finished) -> Void in

I just call the autoScroll() in the viewDidLoad() and the rest takes care of it itself. The speed of the scrolling is decided by the animation time of the UIView. You could (I haven't tried) add an NSTimer with 0 seconds instead so you can invalidate it on userscroll.

Answer #2

For anyone else finding this, I've updated Masa's suggestion to work on Swift and I've introduced a little bit of easing towards the end so it acts more like the original scrollItemsToIndexPath animated call. I have hundreds of items in my view so a steady pace wasn't an option for me.

var scrollPoint: CGPoint?
var endPoint: CGPoint?
var scrollTimer: NSTimer?
var scrollingUp = false

func scrollToIndexPath(path: NSIndexPath) {
    let atts = self.collectionView!.layoutAttributesForItemAtIndexPath(path)
    self.endPoint = CGPointMake(0, atts!.frame.origin.y - self.collectionView!
    self.scrollPoint = self.collectionView!.contentOffset
    self.scrollingUp = self.collectionView!.contentOffset.y > self.endPoint!.y

    self.scrollTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "scrollTimerTriggered:", userInfo: nil, repeats: true)

func scrollTimerTriggered(timer: NSTimer) {
    let dif = fabs(self.scrollPoint!.y - self.endPoint!.y) / 1000.0
    let modifier: CGFloat = self.scrollingUp ? -30 : 30

    self.scrollPoint = CGPointMake(self.scrollPoint!.x, self.scrollPoint!.y + (modifier * dif))
    self.collectionView?.contentOffset = self.scrollPoint!

    if self.scrollingUp && self.collectionView!.contentOffset.y <= self.endPoint!.y {
        self.collectionView!.contentOffset = self.endPoint!
    } else if !self.scrollingUp && self.collectionView!.contentOffset.y >= self.endPoint!.y {
        self.collectionView!.contentOffset = self.endPoint!

Tweaking the values of dif and modifier adjusts the duration/level of ease for most situations.

Answer #3

I use a "1 pixel offset" trick. When scrolling programmatically, just set the end contentOffset.x to 1 pixel more/less than it should. This should be unnoticeable. And in completion block set it to actual value. That way you can avoid the premature cell dequeuing problem and get smooth scrolling animation ;)

Here is an example of scrolling to the right page (e.g. from page 1 to 2). Notice that in the animations: block I actually scrolls one pixel less (pageWidth * nextPage - 1). I then restore the correct value in the completion: block.

CGFloat pageWidth = self.collectionView.frame.size.width;
int currentPage = self.collectionView.contentOffset.x / pageWidth;
int nextPage = currentPage + 1;

[UIView animateWithDuration:1
                     [self.collectionView setContentOffset:CGPointMake(pageWidth * nextPage - 1, 0)];
                 } completion:^(BOOL finished) {
                     [self.collectionView setContentOffset:CGPointMake(pageWidth * nextPage, 0)];