UIScrollView: difference between setContentOffset:animated and scrollRectToVisible:animated - ios

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.

Related

Detect when a user interrupted UIScrollView animation

I have an app with a horizontal scrolling uiscrollview, it shows 3 subviews side by side. I am using Objective C.
When the user drags and releases, the code detects which is the closest subview and automatically scrolls to the nearest of the 3 subviews, making it fully centred.
This all works fine, except;
When the scrollview is animating an auto-centering move, the user can interrupt by just tapping it (not dragging).
This single tap halts the animation and scrollview, leaving it not centred on any of the 3 subviews.
No delegate method I have found can detect when the animation is interrupted and then continue with centering.
How can I do this?
When you start the scroll animation with setContentOffset:animated: or scrollToRectVisible:animated:, its end will be marked by UIScrollViewDelegate method scrollViewDidEndScrollingAnimation:. If before that other delegate method is called, like scrollViewWillBeginDragging: or scrollViewDidEndDecelerating: - that means that user interrupted your animatin with drag (or a tap).
What you want to do is restart your animation when user stops dragging - in scrollViewDidEndDragging:willDecelerate: or scrollViewDidEndDecelerating:
Thanks again mag_zbc!
This really helped me find the solution.
I was overcomplicating the animation, checking for the current position both after the user finished dragging as well as when the animation finished.
To solve it, I just checked the current offset in scrollViewDidEndDecelerating::
-If the final offset was one of the three correct locations (auto-offset finished uninterrupted) then do nothing.
-If the offset was not one of the correct locations (animation was interrupted), then move again.

Very slow deceleration in UIScrollView just in certain cases

I am struggling with aligning a screen to a certain Views inside a UIScrollView. I would like to have same behaviour as paging (same fast and smooth deceleration) but with alignment to a custom views instead of stoping on multiples of the scroll view’s bounds. I have implemented delegate method scrollViewWillEndDragging(_:withVelocity:targetContentOffset:) in order to define my own scroll view’s bounds location. I have also set decelerationRate to UIScrollViewDecelerationRateFast.
It work mostly as desired except the cases when alignment animation is very slow. To be more precise, sometimes, after the dragging is finished, the scrolling animation decelerates to final point very slowly. It finally reach the correct point but after a longer while. I am not able to track down the cases when it happens. I am able to say that everything works fine when final velocity of dragging is zero. Thus, it happens only in some cases when dragging is finished with nonzero velocity.
I wonder if somebody had same problem since I wasn't able to google anything useful. Can you help me please?
I have solved this problem by implementing delegate methods scrollViewDidEndDragging and scrollViewWillBeginDecelerating. Then by using information from scroll view’s pan gesture I found out about translation and I determined final scroll view’s bounds location using setContentOffset method on scroll view.

How can I disable "slingshot" action in iOS gestures?

I'm using a UIPanGesture to move a UITextView and a UIScrollView around on my screen. I only want them to move exactly where they are dragged. But it responds to a "pull down like a slingshot" or a swipe sort of gesture that sends the objects flying across the screen, out of view, and then they slowly drift back into their places within set boundaries. How can I disable these specific gestures? I only want a direct drag and place, no flying across the screen with a flick, swipe, or slingshot.
I'm writing in Objective-C...
Try setting the view that is the one moving, sounds like your scrollView, to scrollView.bounces = NO. Should be one or more scrollViews (or class that inherits from UIScrollView. bounce property creating that effect, the default for this is YES, here is apple docs on UIScrollView's bounce property.
-bounces
Property
A Boolean value that controls whether the scroll view bounces past the edge of content and back again.
Declaration
SWIFT
var bounces: Bool
OBJECTIVE-C
#property(nonatomic) BOOL bounces
Discussion
If the value of this property is YES, the scroll view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is NO, scrolling stops immediately at the content boundary without bouncing. The default value is YES.
Availability
Available in iOS 2.0 and later.

UIPicker within UIScrollView - Ignore drags/moves but capture touches

I have a rather large (in screen-estate) UIControl that I'm putting into a UIScrollView. Being a UIControl, it hijacks all touches and any drag that starts from within the control will not be reflected by the UIScrollView.
This control only needs UIControlEventTouchUpInside, so is there any way to "only" listen to that event?
My first instinct was to override hitTest:withEvent: and return the superview if it detected a drag, but that doesn't provide any information on the type of control event.
My last resort is to make it a generic UIView, but I'd rather not go down that road if possible.
EDIT:
Sorry, for clarity, I purposefully set delaysContentTouches to NO to support other features on the screen I am working on. The unintended side effects result in Joel H's second point.
Try setting both the canCancelContentTouches and delaysContentTouches properties of your UIScrollView to YES.
When the UIScrollView property canCancelContentTouches is set to YES, it should transfer any scrolling/drag touches to the scroll view and cancel whatever that touch would have been registered in the subview.
The scroll view's delaysContentTouches should be set to YES as well. This will prevent drags from being triggered as taps (because the handler fires off too soon).
UIScrollView class reference

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.

Resources