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).
Related
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.
I have a UIScrollView with no vertical scrolling, and on which I force a particular offset (only x) in case the offset tries to become less that that particular offset.
I use setContentOffset:animated: function, with animated argument as YES. The offset is forced correctly.
After I force the offset, 'sometimes' scrolling in the direction opposite to the initial scroll direction gets blocked. Say, I was scrolling with finger pan from left to right, and forced it to some offset, then I can't scroll from right to left anymore.
But the catch is, if I make any tap on the screen, the scrolling starts happening. I am unable to pan, but if I tap the screen, or tap any button, scrolling starts working. If I try to call the button press method programatically after, say 5 seconds of forcing the offset, then it doesn't work. It seems that I need to touch the screen somehow..
I checked the values of contentOffset, contentSize, they seem fine.
PS: there are times when scrollViewDidEndScrollingAnimation: method is not called after forcing the offset, but that is not necessarily the issue.
Edit: Actually, this thing happens when I take the scroll view beyond the threshold offset using my fingers, and keep panning left. At the threshold point, panning stops, but the next time I try to scroll, it doesn't pan. (i.e., I don't give a jerk to go beyond the threshold).
Edit: One more possible loophole: I make scrollEnabled equal to NO just before forcing the new offfset, and just after giving the command to set the new offset, I set it back to YES. I needs to be done so that if I try to scroll the scrollview with a jerk, it doesn't scroll away to the left while trying to set the new offset, since paging is enabled.
Edit: Could it be because I set scrollEnabled to NO while actually scrolling using touch? I do enable it later, but maybe that is some issue...
Important Edit: If I long press on the scrollView, and then try to move, scroll view starts scrolling!
Edit: This code is in scrollViewDidScroll:
if ((theScrollView.contentOffset.x < theScrollView.frame.size.width)
&& [currentlyDisplayedVC isEqual:VC1])
{
//if this is not done, and this call happens when VC3
//is visible a bit too much, scrollView scrolls till VC3.
scrollView.scrollEnabled = NO;
[scrollView setContentOffset:CGPointMake(scrollView.frame.size.width, 0) animated:YES];
scrollView.scrollEnabled = YES;
//this is done so that this block is not reached everytime during scroll animation.
currentlyDisplayedVC = VC2;
}
Then in scrollViewDidEndWithANimation: I add [self VC2reached]
Have you tried using setUserInteractionEnabled:NO and YES to stop/allowing user to interact with the UIScrollView instead of using the scrollEnabled property?
I subclassed UIScrollView and overrode the method touchesShouldBegin:withEvent:inContentView:. In the method I return NO for that particular moment when I see the view stuck, so that all touches are taken in by the scroll view instead of passing them to any other contentView. That solved the problem.
Edit: That doesn't solve the problem actually. I want to disallow touches on contentView ONLY when an attempt to scroll the scrollview is being made, not when some other event like tap on the contentView is attempted. But in ths olution that I posted, all touches will be kept with the scroll view. Do we have a similar touchMoved: method?
Instead of using setContentOffset:animated:
you should try using scrollRectToVisible:animated:
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.
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.
I have two views that flip based on a button press within the view. The view has two CAGradientLayers as sublayers. If I flip immediately after the action method fires, the button is in the process of changing the opacity of the gradients and so you see stuttering (the UIVIew flip animation is having to accommodate the button that is itself changing.)
I can hack a solution by doing a performWithSelection:withObject:afterDelay:0.1f, but it feels like such a hack. I tried setting the layer's needsDisplay property and testing for when it was clears, but probably this is insufficient to tell me the screen itself has redrawn.
Dav
In the end this has no solution. I have since found that when animating a view there are severe performance implication if you also try to animate the super views. You can see this in many iOS apps where the app animates an image in a scrolling list - the scrolling stumbles.
So what I learned is either animate something, or it's view, but not both!