Track UIScrollView contentOffset all the time - ios

I'm using a timer and print the contentOffset.y of a UIScrollView to track it all the time.
But the problems is UIScrollView updates its contentOffset value only after it is decelerated.
I want to know the contentOffset.y value in every millisecond of timer and how can I do that? If I cannot use contentOffset of the UIScrollView at all, is there any other way to track similar value to contentOffset.y of a scrollView?
I'm using swift. Thank you.

Instead of tracking the changes by polling with a timer, you can observe any changes to the value. UIScrollView has a delegate property that gets called for scroll events (see UIScrollViewDelegate), and contentOffset is KVO compliant.
Or you could subclass UIScrollView and try overriding the setters (either for contentOffset or bounds where origin is the the offset, I'm not sure which one, if either, the class calls internally - using the delegate is the preferred solution).

Related

UIScrollView: Only scroll when allowed

I've been trying to only have my UIScrollView scroll in one direction and still get all UITouch events. I'm not fully sure this is possible, at all.
I've tried to override the UIResponder's "touches" events, but as soon as the UIScrollView starts scrolling, the touches methods stop getting called.
I then tried setting the UIScrollView's scrollEnabled property to false until I want the UIScrollView to begin receiving drag events, but I can't seem to find a way to have the UIScrollView to begin receiving touch events mid-drag. Is this possible?
My next attempt was adding "padding" to the scroll view and creating my own "touch events" by playing around with the contentOffset property, but that started calling scrollViewDidScroll: an excessive amount of times and processing like crazy - also some issues with the validity of the responses.
I've played around with hitTest:withEvent:, pointInside:withEvent:, touchesShouldBegin:withEvent:inContentView: and touchesShouldCancelInContentView: without any success.
I've tried a few other things, since, but none have worked. Also, from what I've found, setting delaysContentTouches to false destroys the scroll response.
I would love some real overridden touches methods. Methods that if I don't call super, the UIScrollView doesn't receive the touches.
I need a way to get UITouches (Drags, begins, releases and cancels, not just taps), without affecting the scroll of the UIScrollView, since the UIResponder "touches" events drop off as soon as scrolling starts. Is this possible?
If you just want to detect common touches, try attaching gesture recognizers like UITapGestureRecognizer to your view, they should work even with a UIScrollView.
You can also try placing a transparent UIView on top of the scrollview that detects some touches and passes the rest to the scrollview.
When you start to scroll save off a copy of the offset. Then check against it when you finish scrolling. This code allows scrolling to the left but prevents it from scrolling right
CGPoint oldOffset;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
oldOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < oldOffset.x) {
[scrollView setContentOffset:oldOffset];
}
}

resignFirstResponder when UITextField on UIScrollView is not seen

I have a UITextField on which I call becomeFirstResponder when I load the ViewController. On it, I have a UIScrollView and I would like to call resignFirstResponder on the textField when I have scrolled it out of my view.
The position of UITextField may change, but the behaviour should stay the same. Any ideas on how do you implement this sort of thing? Thanks.
I would look at observing changes to your scrollView's contentOffset property. With knowledge of scrollView's bounds and contentOffset, and textField's frame you could work out if textField is visible. Something like the following should work:
CGRect scrollViewVisibleBounds = CGRectOffset(scrollView.bounds, scrollView.contentsOffset.x, scrollView.contentsOffset.y);
BOOL textFieldIsVisible = CGRectIntersectsRect(textField.frame, scrollViewVisibleBounds);
The problem you have now is how to observe changes to contentOffset. It's not really feasible to be constantly observing (i.e. through KVO), as you'll receive multiple updates per second during, for example, a deceleration animation. I'd look at setting your viewController up as a UIScrollViewDelegate and updating your textField's visibility via callbacks like scrollViewDidEndDragging: and scrollViewDidEndDecelerating.

How to call scrollViewDidScroll: the same way UIScrollView does, but during custom animation?

I have a very large horizontally scrolling UIScrollView which is reusing its subviews (moves and updates them when they are out of visible area, similar like UITableView is reusing cells). This relies on scrollViewDidScroll: delegate call, which gives me actual contentOffset, and here I decide when to reuse particular subview. So far so good.
Sometimes I need to change contentOffset programatically, but with custom animation (inertia and bounce back to the final position). I can do this quite easily using core animation.
The problem is, that during custom animation scrollViewDidScroll: delegate method is not called -> I must do it manually so that subviews reusing works. I tried to call it with timer firing each 0.02 sec. Now there are two problems:
I must get UIScrollView contentOffset using [[_scrollView.layer presentationLayer] bounds].origin.x because during animation normal _scrollView.contentOffset does not change.
However info from presentationLayer is not sufficient for exact syncing - sometimes it is bit late.
problem is when the new contentOffset is far from current position. It looks like built-in UIScrollView animation is CAKeyframeAnimation, and scrollViewDidScroll should is called on key frames positions. But I am not able to get these.
If I rely on timer which is not synced with key frames, views are reused on wrong places and I can not see them at all during animation.
Can anyone shed some light on how and when exactly UIScrollView calls scrollViewDidScroll during setContentOffset:X animated:YES? Is it possible to reproduce it without breaking appstore rules?
First of all, I wouldn't use an NSTimer with a delay of 0.02 secs - that's not what timers are supposed for. Try using a CADisplayLink, it fires once per frame.
In your callback method you can - if your custom animation is running - run your own physics code and call -setContentOffset:animated: respectively. This even allows you to ease exponentially which CA won't let you.

UIScrollView: difference between setContentOffset:animated and scrollRectToVisible:animated

I have read Apple Scroll View Programming Guide for iOS but still confused about the following part:
Scrolling to a Specific Offset
Scrolling to a specific top-left location (the contentOffset property)
can be accomplished in two ways. The setContentOffset:animated: method
scrolls the content to the specified content offset. If the animated
parameter is YES, the scrolling will animate from the current position
to the specified position at a constant rate. If the animated
parameter is NO, the scrolling is immediate and no animation takes
place. In both cases, the delegate is sent a scrollViewDidScroll:
message. If animation is disabled, or if you set the content offset by
setting the contentOffset property directly, the delegate receives a
single scrollViewDidScroll: message. If animation is enabled, then the
delegate receives a series of scrollViewDidScroll: messages as the
animation is in progress. When the animation is complete, the delegate
receives a scrollViewDidEndScrollingAnimation: message.
Making a rectangle visible
It is also possible to scroll a rectangular area so that it is
visible. This is especially useful when an application needs to
display a control that is currently outside the visible area into the
visible view. The scrollRectToVisible:animated: method scrolls the
specified rectangle so that it is just visible inside the scroll view.
If the animated parameter is YES, the rectangle is scrolled into view
at a constant pace. As with setContentOffset:animated:, if animation
is disabled, the delegate is sent a single scrollViewDidScroll:
message. If animation is enabled, the delegate is sent a series of
scrollViewDidScroll: messages as animation progresses. In the case of
scrollRectToVisible:animated: the scroll view’s tracking and dragging
properties are also NO.
If animation is enabled for scrollRectToVisible:animated:, the
delegate receives a scrollViewDidEndScrollingAnimation: message,
providing notification that the scroll view has arrived at the
specified location and animation is complete.
I think it is quite similar between setContentOffset:animated and scrollRectToVisible:animated, could someone give some hints about them?
And scrollRectToVisible:animated: is similar to scrollViewDidEndDecelerating:animated: too.
Short answer:
setContentOffset:animated preserves the zoomscale.
scrollRectToVisible:animated may change it.

Animate while UIScrollView bounces

I want to do a little animation every time the UIScrollView bounces, to be exact, I want a button to move a little bit to the left and then back to it's original position. I use the scrollViewDidScroll method and check if the scrollView's contentOffset is higher than the actual content height and then call the animation. The problem is that the animation is called multiple times during the bounce if I do it like that. Is there a way to only call it once?
Have an ivar or property named something like animationActive that will keep track of animation. When you start it, set animationActive = YES; and when animation completes, set animationActive = NO;. And of course, before you start animation, check if (animationActive == YES).

Resources