24

I'm trying to use the new scrollViewWillEndDragging:withVelocity:targetContentOffset: UIScrollView delegate call in iOS 5 but i can't seem to get it to actually respond to me correctly. I'm changing the targetContentOffset->x value but it never ends up being used. I know the code is being ran because it'll hit breakpoints in that function. I've even tried setting the offset value to a hard coded number so i'd know where it would end up but it never works.

Has anyone been able to use this correctly and make it work? Is there any other delegate call that must be implemented in order for this to work?

Here's my code in case someone sees something wrong with it:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    // goodOffsetX returns the contentOffset i want the scrollView to stop at
    CGFloat goodOffsetX = [self _horizontalContentOffsetForTargetHorizontalContentOffset:(*targetContentOffset).x velocity:velocity.x];

    NSLog( @" " );
    NSLog( @"scrollViewWillEndDragging" );
    NSLog( @"   velocity: %f", velocity.x );
    NSLog( @"   currentX: %f", scrollView.contentOffset.x );
    NSLog( @"   uikit targetX: %f", (*targetContentOffset).x );
    NSLog( @"   pagedX: %f", goodOffsetX );

    targetContentOffset->x = goodOffsetX; 
}
Alex
  • 712
  • 1
  • 5
  • 11
  • 2
    Do you have `pagingEnabled` set to `YES` on this scroll view? – rob mayoff Feb 20 '12 at 20:36
  • 4
    pagingEnabled is set to NO, the docs say it needs to be set to NO in order for this delegate to be called. I also tried setting it to YES just for kicks and it does not get called as per the docs but there is some console logging that says to not do it with pagingEnabled set to YES. – Alex Feb 20 '12 at 20:47
  • My problem was I was testing it on a phone running iOS4. Also, have you set the delegate property of the UIScrollView ? – Marc Apr 04 '12 at 12:40
  • It needs no other delegate methods, and you're right that pagingEnabled must be NO. Are you certain this code doesn't work? You didn't mention what would be the true test: in the didScroll delegate method, you should NSLog contentOffset there. After this method fires, see if the contentOffset doesn't settle on the one you're logging as pagedX. – danh Apr 14 '12 at 05:47

5 Answers5

55

You can implement custom paging with this code:

- (float) pageWidth {
    return ((UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout).itemSize.width +
    ((UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout).minimumInteritemSpacing;
}

- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    CGFloat pageWidth = self.collectionView.frame.size.width + 10 /* Optional Photo app like gap between images. Or use [self pageWidth] in case if you want the next page be also visible */;

    _currentPage = floor((self.collectionView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

    NSLog(@"Dragging - You are now on page %i", _currentPage);
}

- (void) scrollViewWillEndDragging:(UIScrollView*)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint*)targetContentOffset {

    CGFloat pageWidth = self.collectionView.frame.size.width + 10; // [self pageWidth]

    int newPage = _currentPage;

    if (velocity.x == 0) { // slow dragging not lifting finger
        newPage = floor((targetContentOffset->x - pageWidth / 2) / pageWidth) + 1;
    }
    else {
        newPage = velocity.x > 0 ? _currentPage + 1 : _currentPage - 1;

        if (newPage < 0)
            newPage = 0;
        if (newPage > self.collectionView.contentSize.width / pageWidth)
            newPage = ceil(self.collectionView.contentSize.width / pageWidth) - 1.0;
    }

    NSLog(@"Dragging - You will be on %i page (from page %i)", newPage, _currentPage);

    *targetContentOffset = CGPointMake(newPage * pageWidth, targetContentOffset->y);
}

Of course you must set pagingEnabled = NO. _currentPage is a class iVar. Thanks to http://www.mysamplecode.com/2012/12/ios-scrollview-example-with-paging.html for pointing the right way.

m8labs
  • 3,671
  • 2
  • 30
  • 32
  • 6
    This is the best solution I've found by far that emulates the default implementation (only scrolling one page at a time, handling slow drags, etc.) – Nathan Donnellan Aug 26 '13 at 20:20
  • 3
    This works, but if the drag is ended with a too small velocity, it will move far too slowly towards the new page. Any suggestions on how to fix that? – skagedal Nov 08 '15 at 09:02
  • 17
    To answer my own question, set self.scrollView.decelerationRate = UIScrollViewDecelerationRateFast and things will be great! :) – skagedal Nov 08 '15 at 11:14
  • 1
    Works like a charm. Tried to upvote it multiple times. But It wouldn't let me. Kudos to you for the wonderful answer. Thanks. – Alex Dec 05 '15 at 19:41
  • Works perfect. Thanks. – vilanovi May 11 '16 at 11:03
9

SWIFT 3

With a demo here https://github.com/damienromito/CollectionViewCustom

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    let pageWidth = Float(itemWidth + itemSpacing)
    let targetXContentOffset = Float(targetContentOffset.pointee.x)
    let contentWidth = Float(collectionView!.contentSize.width  )
    var newPage = Float(self.pageControl.currentPage)

    if velocity.x == 0 {
        newPage = floor( (targetXContentOffset - Float(pageWidth) / 2) / Float(pageWidth)) + 1.0
    } else {
        newPage = Float(velocity.x > 0 ? self.pageControl.currentPage + 1 : self.pageControl.currentPage - 1)
        if newPage < 0 {
            newPage = 0
        }
        if (newPage > contentWidth / pageWidth) {
            newPage = ceil(contentWidth / pageWidth) - 1.0
        }
    }
    let point = CGPoint (x: CGFloat(newPage * pageWidth), y: targetContentOffset.pointee.y)
    targetContentOffset.pointee = point
}
Damien Romito
  • 9,801
  • 13
  • 66
  • 84
  • It is worth noting, if you have a top or left inset, it needs to be added to the point value before being assigned. – Sethmr Sep 27 '17 at 16:30
6

I was able to run a quick test and got this to correctly fire and make my object stop as desired. I did this using the following simple test:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
     targetContentOffset->x = scrollView.contentOffset.x - 10;    
}

It seems that this method is likely not the issue in your code, but it is more likely that your 'goodOffsetX' is not correctly calculating a valid value to stop at.

heckman
  • 499
  • 3
  • 8
3

Swift 2.2:

extension SomeCollectionViewController {

    override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        let pageWidth = Float(collectionView!.frame.size.width)
        let xCurrentOffset = Float(collectionView!.contentOffset.x)
        currentPage = floor((xCurrentOffset - pageWidth / 2) / pageWidth) + 1
    }

    override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let pageWidth = Float(collectionView!.frame.size.width)
        let targetXContentOffset = Float(targetContentOffset.memory.x)
        let contentWidth = Float(collectionView!.contentSize.width)

        var newPage = currentPage
        if velocity.x == 0 {
            newPage = floor((targetXContentOffset - pageWidth / 2) / pageWidth) + 1
        } else {
            newPage = velocity.x > 0 ? currentPage + 1 : currentPage - 1
            if newPage < 0 {
                newPage = 0
            }
            if newPage > contentWidth / pageWidth {
                newPage = ceil(contentWidth / pageWidth) - 1.0
            }
        }
        targetContentOffset.memory.x = CGFloat(newPage * pageWidth)
    }
}

I also used the collectionView?.decelerationRate = UIScrollViewDecelerationRateFast as suggested by @skagedal to improve page speed.

Glauco Neves
  • 3,483
  • 1
  • 24
  • 36
-1
public func scrollViewWillEndDragging(_ scrollView: UIScrollView ,withVelocity velocity: CGPoint, targetContentOffset: 
UnsafeMutablePointer<CGPoint>){  

let pageWidth = Float(appWidth + itemSpacing)
    let targetXContentOffset = Float(targetContentOffset.pointee.x)
    var newPage = Float(currentPageIndex)

    // I use this way calculate newPage:
    newPage = roundf(targetXContentOffset / pageWidth);

    //if velocity.x == 0 {
    //    newPage = floor( (targetXContentOffset - Float(pageWidth) / 2) / Float(pageWidth)) + 1.0
    //} else {
    //    newPage = Float(velocity.x > 0 ? newPage + 1 : newPage - 1)
    //    if newPage < 0 {
    //        newPage = 0
    //    }
    //    if (newPage > contentWidth / pageWidth) {
    //        newPage = ceil(contentWidth / pageWidth) - 1.0
    //   }
    //}

    let targetOffsetX = CGFloat(newPage * pageWidth)
    let point = CGPoint (x: targetOffsetX, y: targetContentOffset.pointee.y)
    targetContentOffset.pointee = point
 }
buoge
  • 409
  • 5
  • 6