How can I programmatically force-stop scrolling in a UIScrollView?
Asked Answered
S

14

90

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.

Stingo answered 4/8, 2010 at 23:54 Comment(0)
R
114

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];
}
Robertson answered 6/8, 2010 at 6:11 Comment(7)
David: great, thanks. I had twiddled myself in this direction, but ended up with less trivial calculations about picking a 1x1 rect just outside the scroll bounds. Taking your suggestion of offsetting and then restoring immediately (which seems frankly to take advantage of an unpublished behavior where the successive calls in one event run actually work even though the "result" should be a no-op), it works fine. I'm going to edit your answer above to include the generalized solution that should work for any scroll direction. Thanks!Stingo
(Hope you don't mind the edits, wanted to clarify for later travelers. Thanks for your answer!)Stingo
looks great... by the way how do you get back to where you were? do you save the offset before killing it, and then get back there?Farm
the longer one worked fine for me, iOS6.1 on iPod that looks like iPhone 5Turkic
In iOS 7, when the scrollView isDecelerating, only the top solution works (the += 1, -= 1 solution).Churchly
This solution can be improved. Use += 0.1 instead of += 1, so single call of setContentOffset will be enough. Scroll view will round up content offset automatically.Soke
The += bit seems to no longer be necessary under iOS 11 SDK with Xcode 9.2. Was able to get this working with swift using contentTableView.setContentOffset(contentTableView.contentOffset, animated: false)Runkle
C
105

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:

// Objective-C
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
// Swift
scrollView.setContentOffset(scrollView.contentOffset, animated:false)

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

Corella answered 25/4, 2014 at 10:49 Comment(4)
Well done on the explanation to the "reason seekers" among us... (:Crosslink
This kinda makes me sad though.Voyage
I don't understand why this stuff is not documented...urggggghhDenouement
Even more interesting in (at least) iOS 11.3 and 11.4 is: If the contentOffset gets animated to e.g. (300, 0), then "contentOffset = (300, 0)" is assigned while the animation still runs, the contentOffset may reset to (0, 0)! This must be a bug!Overalls
G
74

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.

Goodyear answered 31/1, 2013 at 21:42 Comment(2)
+1 like you said, the other didn't work for me either, but this solution worked great!Fredenburg
Awesome, it works perfectly in supportedInterfaceOrientations to prevent the scroll events to continue if the user is rotating the device while scrolling, which is sometimes a mess depending on what you are doing.Popple
A
21

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
}
Asir answered 20/5, 2015 at 1:48 Comment(4)
this looks like the one apple supposed developers to use. because all others lead to bugs at some point. i am surprised this is not the accepted answer.Binder
This is what this delegate method for. great answer.Vermiform
In my opinion this is the most elegant solution as Apple says in its documentation: "Your application can change the value of the targetContentOffset parameter to adjust where the scrollview finishes its scrolling animation.". Worked very well for meUnman
this is the right answer. None of the above worked for meRobinette
P
21

Stop the scroll in swift:

scrollView.setContentOffset(scrollView.contentOffset, animated: false)
Phonetics answered 2/11, 2016 at 19:23 Comment(0)
R
16

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];
Rubetta answered 13/7, 2014 at 11:42 Comment(3)
Well I use this in an App of mine and it does exactly what is asked by the OP. Can you please give a bit more context? What is not working or why?Rubetta
Oh sorry ... I think I know ... I just answered the part of the question that I was looking for ... the second part I overlooked ... :-) --> edited.Rubetta
Doesn't stop deceleration.Collude
A
5

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;
Atwell answered 4/8, 2010 at 23:59 Comment(6)
Thanks. While this halts the visible scrolling, it doesn't actually stop the internal deceleration. If you toggle this on, and then off again after a moment, you'll see the scrolling pause, and then jump ahead and continue to decelerate. So the scrollview's internal state is still scrolling. Thus you need to freeze until you know deceleration has stopped, which you can do in partnership with a delegate, but it feels a little goofy to me. I'm hoping for something that actual just zaps the deceleration immediately (e.g. has the effect of a single tap on the screen).Stingo
ok, these kind of issues sound familiar from prior experience... why not zap the deceleration by setting decelerationRate = 1e10; while freeze == YES?Atwell
(not knowing the internal math, 10*UIScrollViewDecelerationRateFast may be a wiser choice than 1e10)Atwell
Interesting idea! Unfortunately this value seems to be clamped. Tried it out, and there doesn't seem to be a value of this which will cause deceleration to immediately stop. Best I can do is make it sluggish. :)Stingo
plan B is then to disable scrolling for a brief period - maybe you can get away with it, since you're blocking setContentOffset anyway, hopefully preventing side-effects from disabling scrolling.Atwell
Apparently the deceleration works multiplicatively. If you print out the deceleration rates they define, it gives fast = 0.99, and normal = 0.998. Even so though, apparently setting deceleration to 0 doesn't work, sadly.Robertson
R
4

This answer worked for me: Deactivate UIScrollView decelerating

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    [scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
Rabbet answered 10/7, 2015 at 11:48 Comment(1)
This could possibly stops the scrollView during the bounceTrine
I
3

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)
    }
}
Illfavored answered 31/10, 2019 at 7:29 Comment(0)
A
2

SWift 5+

by using Extension

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

use

    myScrollView.stopDecelerating()
    // your stuff
Adalie answered 4/12, 2020 at 10:47 Comment(0)
K
1

I have tried this methods in collectionview:

self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()

Kinfolk answered 5/12, 2016 at 12:41 Comment(0)
T
1

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;

    }
}
Thorathoracic answered 23/6, 2019 at 16:49 Comment(0)
W
0

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.

Weksler answered 26/7, 2019 at 23:40 Comment(0)
L
0

Swift 5, Xcode 13

In my example, I have the situation where the user has a scroll view and image inside, and when the user begins zooming the image need prevent free-scrolling inside, I use the delegate function of the scroll view and content offset. Don't forget scrollView.delegate = self, if need full code don't shy notify my My code:

private var contentOffsetHolder: CGPoint = CGPoint(x: 0, y: 0)
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        contentOffsetHolder = scrollView.contentOffset
        
    }
    
    func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
        scrollView.setContentOffset(contentOffsetHolder, animated:false)
    }
Lizarraga answered 5/6, 2022 at 10:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.