83
votes

Note: The answer given here doesn't work for me.

I have a UIScrollView (not a table view, just a custom thing), and when the user takes certain actions, I want to kill any scrolling (dragging or deceleration) inside the view. I've tried doing e.g. this:

[scrollView scrollRectToVisible:CGRectInset([scrollView bounds], 10, 10) animated:NO];

on the theory that, given a rect that's already known visible, the scrolling will just stop where it is, but it turns out that this doesn't have any effect-- apparently the scroll view sees that the given rect is in bounds and takes no action. I can get the scroll to stop, if I give a rect that is definitely outside the currently-visible bounds, but inside the contentSize of the view. This seems to halt the view as expected... but also causes it to jump to some other location. I could probably do a little playing around at the margins to get this to work reasonably OK, but does anyone know of a clean way to halt a scroll view that's doing its thing?

Thanks.

13

13 Answers

109
votes

I played with your original solution a bit, and this seems to work just fine. I think you almost had it, but you were just offsetting the rect that you used too much, and forgot that you could just scroll the rect straight back to the original rect.

The generalized solution for any scrolling action is this:

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    offset.x -= 1.0;
    offset.y -= 1.0;
    [scrollView setContentOffset:offset animated:NO];
    offset.x += 1.0;
    offset.y += 1.0;
    [scrollView setContentOffset:offset animated:NO];
}

[Edit] As of iOS 4.3 (and possibly earlier) this also appears to work

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    [scrollView setContentOffset:offset animated:NO];
}
95
votes

The generic answer is, that [scrollView setContentOffset:offset animated:NO] is not the same as [scrollView setContentOffset:offset] !

  • [scrollView setContentOffset:offset animated:NO] actually stops any running animation.
  • [scrollView setContentOffset:offset] doesn't stop any running animation.
  • Same for scrollView.contentOffset = offset: doesn't stop any running animation.

That's not documented anywhere, but that's the behavior as tested on iOS 6.1 & iOS 7.1 - probably also before.

So the solution to stop a running animation / deceleration is simple as that:

[scrollView setContentOffset:scrollView.contentOffset animated:NO];

Basically what David Liu said in his edited answer. But I wanted to make clear, that these two APIs are NOT the same.

Swift 3:

scrollView.setContentOffset(scrollView.contentOffset, animated:false)
69
votes

For me, David Lui's accepted answer above didn't work for me. This is what I ended up doing:

- (void)killScroll {
    self.scrollView.scrollEnabled = NO;
    self.scrollView.scrollEnabled = YES;
}

For what it is worth, I'm using the iOS 6.0 iPhone Simulator.

22
votes

This is what I do for my scroll views and all other related subclasses:

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

This sets the targetContentOffset to the scrollView's current offset, thus making the scrolling to stop because it has reached the target. It actually makes sense to use a method whose purpose is that users could set the targeted contentOffset.

Swift

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
}
21
votes

Stop the scroll in swift:

scrollView.setContentOffset(scrollView.contentOffset, animated: false)
18
votes

Actually ... The most "modern" way would be -->

scrollview.panGestureRecognizer.enabled = false;
scrollview.panGestureRecognizer.enabled = true;

This deactivates the gesture-recognizer that is responsible for scrolling for just a moment which will kill the current touch. The user would need to lift the finger and put it back down to start scrolling again.

Edit: This actually just kills the current dragging of the user but does not immediately stop the deceleration if the scrollview is in this state currently. To do this the accepted answers edit is pretty much the best way xD

[scrollview setContentOffset: scrollview.contentOffset animated:false];
5
votes

The cleanest way will be subclassing UIScrollView and providing your own setContentOffset method. This should pass the message on, only if you haven't switched on your freeze boolean property.

Like so:

BOOL freeze; // and the @property, @synthesize lines..

-(void)setContentOffset:(CGPoint)offset
{
    if ( !freeze ) [super setContentOffset:offset];
}

Then, to freeze:

scrollView.freeze = YES;
4
votes

This answer worked for me: Deactivate UIScrollView decelerating

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    [scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
3
votes

Disable just scroll user interaction. (swift)

scrollView.isScrollEnabled = false

Disable in during scroll animation after dragging. (swift)

var scrollingByVelocity = false

func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
    if !scrollingByVelocity {
        scrollView.setContentOffset(scrollView.contentOffset, animated: false)
    }
}
1
votes

SWift 5+

by using Extension

extension UIScrollView  {
    
    func stopDecelerating() {
        let contentOffset = self.contentOffset
        self.setContentOffset(contentOffset, animated: false)
    }
}

use

    myScrollView.stopDecelerating()
    // your stuff
0
votes

I have tried this methods in collectionview:

self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()

0
votes

This works for me in Swift 4.2:

   func killScroll() {
    self.scrollView.isScrollEnabled = false;
    self.scrollView.isScrollEnabled = true;
}

... as an extension:

extension UIScrollView {
    func killScroll() {
        self.isScrollEnabled = false;
        self.isScrollEnabled = true;

    }
}
0
votes

I wanted to disable scrolling only when a certain UIView within the scrollview is the source of the touch during the swipe. It would have required quite a bit of refactoring to move the UIView outside of the UIScrollView, as we had a complex view hierarchy.

As a workaround, I added a single UIPanGestureRecognizer to the subview in which I wanted to prevent from scrolling. This UIPanGestureRecognizer will cancelsTouchesInView which prevents the UIScrollView's panGesture from activating.

It's a little bit of a 'hack', but it's a super easy change, and if you're using a XIB or Storyboard, all you need to do is drag the pan gesture onto the subview in question.