Setting contentOffset programmatically triggers scrollViewDidScroll
IosUiscrollviewUiscrollviewdelegateIos Problem Overview
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
and scrollViewDidScroll:
to track movement. I query the contentOffset
of the UIScrollView
which changed and then reflect change to other scroll views by setting their contentOffset
property to match.
Great.... except I noticed a lot of extra calls. Programmatically changing the contentOffset
of my scroll views triggers the delegate method scrollViewDidScroll:
to be called. I've tried using setContentOffset: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 custom UIView
which uses delegate pattern to call back to the presenting UIViewController
subclass that handles coordinating the various contentOffset
values.
Ios Solutions
Solution 1 - Ios
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 - Ios
Try
id scrollDelegate = scrollView.delegate;
scrollView.delegate = nil;
scrollView.contentOffset = point;
scrollView.delegate = scrollDelegate;
Worked for me.
Solution 3 - Ios
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 - Ios
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 - Ios
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
Solution 6 - Ios
This is not a direct answer to the question, but if you are getting what appear to be spurious such messages, it can ALSO be because you are changing the bounds. I am using some Apple sample code with a "tilePages" method that removes and adds subview to a scrollview. This infrequently results in additional scrollViewDidScroll: messages called immediately, so you get into a recursion which you for sure didn't expect. In my case I got a nasty impossible to find crash.
What I ended up doing was queuing the call on the main queue:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(scrollView == yourScrollView) {
// dispatch fixes some recursive call to scrollViewDidScroll in tilePages (related to removeFromSuperView)
// The reason can be found here: http://stackoverflow.com/questions/9418311
dispatch_async(dispatch_get_main_queue(), ^{ [self tilePages]; });
}
}