I would like to be able to move a subview on and off the screen much like you browse between images in the iPhone's build in Photos app, so if the subview is more than 1/2 off screen when I let go with my finger it must animate off screen, but it must also support swipe so if the swipe/pan velocity is high enough it must animate off screen even though is might be less than 1/2 off screen.
My idea was to use UIPanGestureRecognizer and then test on the velocity. This works, but how do I set a correct animation duration for the moving of the UIView based on the view's current location and velocity of the pan so that it seems smooth? If I set a fixed value, the animation either starts to slow or to fast compared to my fingers swipe speed.
You don't need to use [weak self] in static function UIView. animate() You need to use weak when retain cycle is possible and animations block is not retained by self.
The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] . It's best to let the OS run your animations; the animation thread does not block the main thread -- only the animation block itself.
UIView. animate runs on the main thread and is asynchronous.
An observation on relying upon the velocity in UIPanGestureRecognizer
: I don't know about your experience, but I found the system generated velocity on the simulator to be not terribly useful. (It's ok on the device, but problematic on simulator.)
If you pan quickly across and abruptly stop, wait, and only then end the gesture (e.g. user starts a swipe, realizes that this wasn't what they wanted, so they stop and then release their finger), the velocity reported by velocityInView:
at the UIGestureRecognizerStateEnded
state when your finger was released seems to be the fast speed before I stopped and waited, whereas the right velocity in this example would be zero (or near zero). In short, the reported velocity is that which it was right before the end of the pan, but not the speed at the end of the pan itself.
I ended up calculating the velocity myself, manually. (It seems silly that this is necessary, but I didn't see any way around it if I really wanted to get the final speed of the pan.) Bottom line, when the state is UIGestureRecognizerStateChanged
I keep track of the current and previous translationInView
CGPoint as well as the time, and then using those values when I was in the UIGestureRecognizerStateEnded
to calculate the actual final velocity. It works pretty well.
Here is my code for calculating the velocity. I happen to not be using velocity to figure out the speed of the animation, but rather I'm using it to determine whether the user either panned far enough or flicked quickly enough for the view to move more than half way across the screen and thus triggering the animation between views, but the concept of calculating the final velocity seems applicable to this question. Here's the code:
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture { static CGPoint lastTranslate; // the last value static CGPoint prevTranslate; // the value before that one static NSTimeInterval lastTime; static NSTimeInterval prevTime; CGPoint translate = [gesture translationInView:self.view]; if (gesture.state == UIGestureRecognizerStateBegan) { lastTime = [NSDate timeIntervalSinceReferenceDate]; lastTranslate = translate; prevTime = lastTime; prevTranslate = lastTranslate; } else if (gesture.state == UIGestureRecognizerStateChanged) { prevTime = lastTime; prevTranslate = lastTranslate; lastTime = [NSDate timeIntervalSinceReferenceDate]; lastTranslate = translate; [self moveSubviewsBy:translate]; } else if (gesture.state == UIGestureRecognizerStateEnded) { CGPoint swipeVelocity = CGPointZero; NSTimeInterval seconds = [NSDate timeIntervalSinceReferenceDate] - prevTime; if (seconds) { swipeVelocity = CGPointMake((translate.x - prevTranslate.x) / seconds, (translate.y - prevTranslate.y) / seconds); } float inertiaSeconds = 1.0; // let's calculate where that flick would take us this far in the future CGPoint final = CGPointMake(translate.x + swipeVelocity.x * inertiaSeconds, translate.y + swipeVelocity.y * inertiaSeconds); [self animateSubviewsUsing:final]; } }
The docs say
The velocity of the pan gesture, which is expressed in points per second. The velocity is broken into horizontal and vertical components.
So I'd say, given you want to move your view xPoints
(measured in pt) to let it go off-screen, you could calculate the duration for that movement like so:
CGFloat xPoints = 320.0; CGFloat velocityX = [panRecognizer velocityInView:aView].x; NSTimeInterval duration = xPoints / velocityX; CGPoint offScreenCenter = moveView.center; offScreenCenter.x += xPoints; [UIView animateWithDuration:duration animations:^{ moveView.center = offScreenCenter; }];
You might want to use + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
instead though and try out different UIViewAnimationOptions
.
In most cases setting UIViewAnimationOptionBeginFromCurrentState
option for UIView
animator is enough to make a flawlessly continued animation.
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