Setting contentOffset programmatically triggers scrollViewDidScroll
Solution 1
It is possible to change the content offset of a UIScrollView
without triggering the delegate callback scrollViewDidScroll:
, by setting the bounds of the UIScrollView
with the origin set to the desired content offset.
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = desiredContentOffset;
scrollView.bounds = scrollBounds;
Solution 2
Try
id scrollDelegate = scrollView.delegate;
scrollView.delegate = nil;
scrollView.contentOffset = point;
scrollView.delegate = scrollDelegate;
Worked for me.
Solution 3
What about using existing properties of UIScrollView?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating) {
/// The content offset was changed programmatically.
/// Your code goes here.
}
}
Solution 4
Another approach is to add some logic in your scrollViewDidScroll delegate to determine whether or not the change in content offset was triggered programatically or by the user's touch.
- Add an 'isManualScroll' boolean variable to your class.
- Set its initial value to false.
- In scrollViewWillBeginDragging set it to true.
- In your scrollViewDidScroll check to see that is it true and only respond if it is.
- In scrollViewDidEndDecelerating set it to false.
- In scrollViewWillEndDragging add logic to set it to false if the velocity is 0 (as scrollViewDidEndDecelerating won't be called in this case).
Solution 5
Simplifying @Tark's answer, you can position the scrollview without firing scrollViewDidScroll
in one line like this:
scrollView.bounds.origin = CGPoint(x:0, y:100); // whatever values you'd like
Comments
-
DBD almost 2 years
I've got a a few
UIScrollView
on a page. You can scroll them independently or lock them together and scroll them as one. The problem occurs when they are locked.I use
UIScrollViewDelegate
andscrollViewDidScroll:
to track movement. I query thecontentOffset
of theUIScrollView
which changed and then reflect change to other scroll views by setting theircontentOffset
property to match.Great.... except I noticed a lot of extra calls. Programmatically changing the
contentOffset
of my scroll views triggers the delegate methodscrollViewDidScroll:
to be called. I've tried usingsetContentOffset:animated:
instead, but I'm still getting the trigger on the delegate.How can I modify my contentOffsets programmatically to not trigger
scrollViewDidScroll:
?Implementation notes.... Each
UIScrollView
is part of a customUIView
which uses delegate pattern to call back to the presentingUIViewController
subclass that handles coordinating the variouscontentOffset
values. -
Tark about 12 yearswhy so leery my man? it is all the content offset is really, and can be animated using UIView animation blocks and stuff.
-
DBD about 12 yearsInteresting, while not my issue in this case I'll keep this in mind.
-
Admin about 12 yearsThis still makes another call to scrollViewDidScroll, but it does not do so until after the current scrollViewDidScroll function ends.
-
DBD over 9 yearsPossible, but fragile. If the
UIScrollView
API changed by even adding a new similar property it would cause your code to break. You would also have to worry about the flag these flags being gone. After all, I was checking in "did change" and the scroll view might no longer have the flag because the action is complete. -
SimplGy about 9 years@Mark -- I don't see this queuing a call to
scrollViewDidScroll
. Is there some situation this would happen in? -
Shaheen Ghiassy over 8 years@DBD - If you worried about about every API changing in UIKit I don't know how you would get anything done. Relying on publicly documented APIs is not fragile.
-
DBD over 8 years@Shaheen - I'm not saying the solution is made of spun glass, but I found it to be less future proof than desired. The premise of the solution is checks 3 potential activities and if it's not those activities assume by process of elimination it is the 4th. My concern isn't about Apple just changing a public API, it's about Apple adding functionality to the API which would require a logic update in order for the code to continue functioning and Apple does make additions to the UIKit quite regularly, both as new classes and additions to existing classes. It's ok if you don't like my reasons.
-
Shaheen Ghiassy over 8 years@DBD - I can agree with you that "The premise of the solution is checks 3 potential activities and if it's not those activities assume by process of elimination it is the 4th"is less than desirable logic.
-
Blacky over 8 yearsit's the only solution who can handle animated offset as well, all the other solution disable animation.
-
Marchy over 7 yearsBest answer on here by far. Unlike the bounds solution, it is not some mysterious side-effect that absolutely COULD change with future behaviour, and the likelihood of scroll-view having new concepts introduced is really low unless new touch interaction kinds get introduced – which let's face it, 10 years into iOS (we're even passed force touch now) is highly unlikely to happen. And if it does... big whoop, add one more interaction to your code and you're done. Could even spin it into an extension (with 'ieUserInteracting' property) and handles all your scroll views in one shot. Done.
-
brandonscript over 5 yearsNot sure what's going on, but in Xcode 9 - building for iOS 11, even when setting bounds,
scrollViewDidScroll
appears to fire. -
Chris Chute almost 4 yearsBest answer here. One modification though: I'd use scrollViewDidEndDragging(_:willDecelerate:) instead of scrollViewWillEndDragging. Then instead of checking that the velocity is 0, you check that decelerate is false, which explicitly tells you that scrollViewWillEndDecelerating will not get called.
-
hbtpoprock almost 3 yearsThis ans works with tableView triggering scrollViewDidScroll problem as well
-
aheze over 2 yearsThis works perfectly, thanks!