I have spent the past 12 hours futzing with this and I'm going braindead.
view on bitbucket:
https://bitbucket.org/burneraccount/scrollviewexample
git https:
https://bitbucket.org/burneraccount/scrollviewexample.git
git ssh:
[email protected]:burneraccount/scrollviewexample.git
^ That is a condensed, self-contained Xcode project that exemplifies the problem I'm having in my real work.
Summary
I am trying to achieve a static-image effect, where the image (a rock in the example) appears stuck to the screen while the lower content scrolls upwards appearing to go above the scrollview.
There's a screen-sized (UIScrollView*)mainScrollView
. This scrollview has a (UIView*)contentView
. contentView
is ~1200 pts long and has two subviews: (UIScrollView*)imageScroller and (UITextView*)textView.
All works well until you scroll up a little bit, then scroll down really fast. The resulting position after movement stops is incorrect. It somehow doesn't update properly in the scrollviewDidScroll
delegate method. The fast downward scroll (only after you've dragged up) behaves somewhat correctly, but still results in a misplaced view:
gently dragging does almost nothing, and the velocity of the scroll is directly related to the incorrect offset.
Here is the offending code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.mainScrollView)
{
CGFloat mainOffset = self.mainScrollView.contentOffset.y;
if (mainOffset > 0 && mainOffset < self.initialImageScrollerFrame.size.height)
{
CGRect imageFrame = self.imageScroller.frame;
CGFloat offset = mainOffset-self.lastOffset;
self.imageScroller.frame = CGRectMake(imageFrame.origin.x,
imageFrame.origin.y + offset, // scroll up
imageFrame.size.width,
imageFrame.size.height - offset); // hide the bottom of the image
self.lastOffset = mainOffset;
}
}
}
I've restructured this in almost every way I could imagine and it always has similar effects. The worst part is how the very simple and straightforward code fails to do what is seems like it's guaranteed to do.
Also odd is that the zoom-effect I use in my real project works fine using the same mechanism to size the same view element. It runs inside (contentOffset < 0)
instead of (contentOffset > 0)
, so it only zoom in on the imageScroller
when you're pulling the view below it's normal offset. That leads me to conspire that some data is lost as it crosses the contentOffset 0
, but my conspiracies have been shot down all day.
This is example is a lot less complicated than the real project I'm working on, but it properly reproduces the problem. Please let me know if you can't open my project, or can't connect to the repo.
There is likely a largely obvious fix for this, but I am hours past the point of having any new perspective. I will be thoroughly amazed if I ever get this to work.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.mainScrollView)
{
CGFloat mainOffset = self.mainScrollView.contentOffset.y;
if (mainOffset >= 0 && mainOffset < self.initialImageScrollerFrame.size.height)
{
CGRect imageFrame = self.imageScroller.frame;
CGFloat offset = self.mainScrollView.contentOffset.y-self.lastOffset;
if ( imageFrame.origin.y + offset > 0) { //[2]
self.imageScroller.frame = CGRectMake(imageFrame.origin.x,
imageFrame.origin.y + offset,
imageFrame.size.width,
imageFrame.size.height - offset);
self.lastOffset = mainOffset;
}
}else if (mainOffset < 0) { //[1]
self.imageScroller.frame = self.initialImageScrollerFrame;
}
}
}
[1] - When your mainOffset
goes below zero, you need to reset your imageScroller frame. ScrollViewDidScroll does not register with every pixel's-worth of movement, it is only called every so often (try logging it, you will see). So as you scroll faster, it's offsets are further apart. This can result in a positive scroll of say +15 becoming a negative scroll on the next call to scrollViewDiDScroll. So your imageScroller.frame.y may get stuck on that +15 offset even when you expect it to be zero.Thus as soon as you detect a negative value for mainOffset, you need to reset your imageScroller frame.
[2] - similarly here you need to ensure here that your imageScroller.frame.y will always be +ve.
These fixes do the job, but I would still consider them "ugly and not production-worthy". For a cleaner approach you might want to reconsider the interplay between these views, and what you are trying to achieve.
By the way, your 'image.png' is actually a jpg. While these renders fine on the simulator, it will break (with compiler errors) on a device.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With