Assign proper velocity to pan gesture iOS? - ios

I have a tableView with a panGesture. I am able to pan this tableView from bottom part of the screen vertically. However, I want the panGesture to continue with a certain velocity, to get a scrollView kind of effect while panning up/down.
What happens is that the tableView pans very quickly in upwards or downwards direction with no sign of stopping.
Here's what I have done so far :
-(void)handlePanWithVelocity:(UIPanGestureRecognizer *)gestureRecognizer{
CGPoint displacement;
CGPoint velocity = [gestureRecognizer velocityInView:self.mapView];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:{
break;
};
case UIGestureRecognizerStateChanged:{
CGPoint translation = [gestureRecognizer translationInView:self.view];
displacement = (isVerticalPan) ? CGPointMake(0, translation.y) : CGPointMake(translation.x, 0);
mytableView.transform = CGAffineTransformMakeTranslation(displacement.x, displacement.y);
break;
};
case UIGestureRecognizerStateEnded: {
displacement.y = displacement.y + velocity.y;
[UIView animateWithDuration:1 animations:^{
mytableView.transform = CGAffineTransformMakeTranslation(displacement.x, displacement.y);
}];
break;
};
case UIGestureRecognizerStatePossible:break;
case UIGestureRecognizerStateCancelled:break;
case UIGestureRecognizerStateFailed:break;
}
}
What can be done to improve the current behaviour?

The thing is that you dont measure the time that it takes to move in the first place. Since velocity it x/t you need to adjust the duration of you UIView animation. You have to measure the time between the Pan Recognizer callbacks and then calculate the speed.
global variables:
double velocity;
NSDate *date;
and then:
-(void)handlePanWithVelocity:(UIPanGestureRecognizer *)gestureRecognizer{
CGPoint displacement;
CGPoint velocity = [gestureRecognizer velocityInView:self.mapView];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:{
break;
};
case UIGestureRecognizerStateChanged:{
CGPoint translation = [gestureRecognizer translationInView:self.view];
displacement = (isVerticalPan) ? CGPointMake(0, translation.y) : CGPointMake(translation.x, 0);
mytableView.transform = CGAffineTransformMakeTranslation(displacement.x, displacement.y);
if(date){
double timePassed_ms = [date timeIntervalSinceNow] * -1000.0;
velocity = displacement.y/timePassed_ms;
}
date = [NSDate date];
break;
};
case UIGestureRecognizerStateEnded: {
displacement.y = displacement.y + velocity.y;
[UIView animateWithDuration:1 animations:^{
mytableView.transform = CGAffineTransformMakeTranslation(0, velocity);
}];
break;
};
case UIGestureRecognizerStatePossible:break;
case UIGestureRecognizerStateCancelled:break;
case UIGestureRecognizerStateFailed:break;
}
}
I didnt test the code but you get the general idea.

Related

UIPanGestureRecognizer not working on iPad?

I have a UIPanGestureRecognizer code below:
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panThis:)];
self.panRecognizer.delegate = self;
[self.view addGestureRecognizer:self.panRecognizer];
- (void)panThis:(UIPanGestureRecognizer *)recognizer
{
switch (recognizer.state)
{
case UIGestureRecognizerStateBegan:
{
CGPoint panStartPoint = [recognizer translationInView:self.view];
NSLog(#"Pan Began at %#", NSStringFromCGPoint(panStartPoint));
break;
}
default:
break;
}
}
It works find on iPhone, but on iPad, it can't recognize the pan gesture and keep logging
Pan Began at {0, 0}
Pan Began at {0, 0}
Pan Began at {0, 0}
Does anything wrong with my code? Thanks a lot
this is my code.. it works. give a try.
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
NSLog(#"handlePan");
//CGPoint p;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
NSLog(#"UIGestureRecognizerStateBegan");
break;
case UIGestureRecognizerStateEnded:
// All fingers are lifted.
break;
default:
break;
}
UIView * inView = recognizer.view;
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
///relative to view.
CGPoint center = inView.center;
NSLog(#"handlePan %f %f", center.x, center.y);
}

Subviews moving out of place when parent view is rotated

I'm following this guide here: http://guti.in/articles/creating-tinder-like-animations/ in order to recreate a Tinder-like swiping card effect.
Long story short, I have a UIView with a PanGestureRecognizer set to do the following:
- (void)dragged:(UIPanGestureRecognizer *)gestureRecognizer
{
CGFloat xDistance = [gestureRecognizer translationInView:self].x;
CGFloat yDistance = [gestureRecognizer translationInView:self].y;
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:{
self.originalPoint = self.center;
break;
};
case UIGestureRecognizerStateChanged:{
CGFloat rotationStrength = MIN(xDistance / 320, 1);
CGFloat rotationAngel = (CGFloat) (2*M_PI * rotationStrength / 16);
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
CGFloat scale = MAX(scaleStrength, 0.93);
self.center = CGPointMake(self.originalPoint.x + xDistance, self.originalPoint.y + yDistance);
CGAffineTransform transform = CGAffineTransformMakeRotation(rotationAngel);
CGAffineTransform scaleTransform = CGAffineTransformScale(transform, scale, scale);
self.transform = scaleTransform;
break;
};
case UIGestureRecognizerStateEnded: {
[self resetViewPositionAndTransformations];
break;
};
case UIGestureRecognizerStatePossible:break;
case UIGestureRecognizerStateCancelled:break;
case UIGestureRecognizerStateFailed:break;
}
}
- (void)resetViewPositionAndTransformations
{
[UIView animateWithDuration:0.2
animations:^{
self.center = self.originalPoint;
self.transform = CGAffineTransformMakeRotation(0);
}];
}
However, unlike the view in the example, mine has multiple different subviews, and I'm finding that when I apply this code, I get this erroneous result:
Before Swipe:
During Swipe:
As you can see, it seems as if the subviews are "sliding" out of place, so to speak. What's worse, when the swipe is over, the subviews are left looking slightly out of place, even though the parent view is in the right place. If you're curious, my constraints look like:
If anyone could help me get the subviews to move correctly with the superview, I would really appreciate it. One thing I've found sort-of remedies the last problem is to store the original centers of each of the subviews and restore them to that at the end, but I'm sure there must be a better way.
EDIT: I'm still haven't come up with a fix, but I'm now 99.9% sure it's to do with the constraints of the subviews.

Animation from Pan Gesture begins from initial state instead of current state

In my application I have a view that sits on top of another view. The user is able to swipe the top view to the right, which fades it out and "fades in" the back view.
When the user pans to the right and lets go, the animation kicks in and it should translate from its current position to the new off screen position. The problem is that the animation instead snaps the view back to its starting point and then does the animation.
Here is my code for the pan gesture and animation:
- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view];
CGPoint velocity = [recognizer velocityInView:recognizer.view];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
[recognizer setTranslation:CGPointMake(self.slidingView.frame.origin.x, 0) inView:recognizer.view];
break;
}
case UIGestureRecognizerStateChanged: {
[self.slidingView setTransform:CGAffineTransformMakeTranslation(MAX(0,translation.x), 0)];
CGFloat percentage = fmaxf(0,(translation.x/recognizer.view.bounds.size.width));
[self.backgroundBlurImageView setAlpha:1-percentage];
[self.slidingView setAlpha:1-percentage];
[self.backgroundImageView setTransform:CGAffineTransformMakeScale(1 + (percentage * 0.05), 1 + (percentage * 0.05))];
[self.backgroundBlurImageView setTransform:CGAffineTransformMakeScale(1 + (percentage * 0.05), 1 + (percentage * 0.05))];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
if (velocity.x > 5.0 || (velocity.x >= -1.0 && translation.x > kMenuViewControllerMinimumPanDistanceToOpen * self.slidingView.bounds.size.width)) {
CGFloat transformedVelocity = velocity.x/ABS(self.slidingView.bounds.size.width - translation.x);
CGFloat duration = 0.66;
[self showViewAnimated:YES duration:duration initialVelocity:transformedVelocity];
} else {
[self hideViewAnimated:YES];
}
}
default:
break;
}
}
- (void)showViewAnimated:(BOOL)animated duration:(CGFloat)duration
initialVelocity:(CGFloat)velocity;
{
// animate
__weak typeof(self) blockSelf = self;
[UIView animateWithDuration:animated ? duration : 0.0 delay:0
usingSpringWithDamping:0.8f initialSpringVelocity:velocity options:UIViewAnimationOptionAllowUserInteraction animations:^{
blockSelf.slidingView.transform = CGAffineTransformMakeTranslation(blockSelf.slidingView.bounds.size.width, 0);
[blockSelf.backgroundBlurImageView setTransform:CGAffineTransformMakeScale(1.05, 1.05)];
[blockSelf.backgroundImageView setTransform:CGAffineTransformMakeScale(1.05, 1.05)];
[blockSelf.backgroundBlurImageView setAlpha:0];
[blockSelf.slidingView setAlpha:0];
} completion:^(BOOL finished) {
blockSelf.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapOnAaron:)];
[blockSelf.view addGestureRecognizer:self.tapGestureRecognizer];
}];
}
- (void)hideViewAnimated:(BOOL)animated;
{
__weak typeof(self) blockSelf = self;
[UIView animateWithDuration:0.3f animations:^{
blockSelf.slidingView.transform = CGAffineTransformIdentity;
[blockSelf.backgroundBlurImageView setTransform:CGAffineTransformIdentity];
[blockSelf.backgroundImageView setTransform:CGAffineTransformIdentity];
[blockSelf.backgroundBlurImageView setAlpha:1];
[blockSelf.slidingView setAlpha:1];
} completion:^(BOOL finished) {
[blockSelf.view removeGestureRecognizer:self.tapGestureRecognizer];
blockSelf.tapGestureRecognizer = nil;
}];
}
I have already tried settings the animation options to:
UIViewAnimationOptionBeginFromCurrentState
with no effect.
Anyone have a clue what I am missing? Thanks
Kyle, sometimes this sort of thing happens if you are using autolayout. If you don't need it try disabling it and see if it fixes it.

UIPanGestureRecognizer - translationInView with CATransform3D

I'm trying to implement a page flipping animation using a UIPanGestureRecognizer. Here's the code:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:self.view];
CGPoint translation = [recognizer translationInView:self.view];
CGPoint velocity = [recognizer velocityInView:self.view];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
switch (recognizer.state) {
case UIGestureRecognizerStateChanged:
{
self.currentTranslation = self.currentTranslation + translation.x;
//only pan left
if (self.currentTranslation > 0.0) {
self.currentTranslation = 0.0;
}
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -2000;
self.currentRotation = self.currentTranslation/2 * M_PI / 180.0f;
//dont rotate past -90 degrees
if (self.currentRotation <= -M_PI/2) {
self.currentRotation = -M_PI/2+0.0001;
}
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, self.currentRotation, 0.0f, 1.0f, 0.0f);
float ypos = self.view.layer.position.y;
self.view.layer.anchorPoint = CGPointMake(0, 0.5);
self.view.layer.position = CGPointMake(0, ypos);
self.view.layer.transform = rotationAndPerspectiveTransform;
break;
}
default:
break;
}
}
The animation works, and the page turns as I drag to the left. However, even when I move at a steady, slow pace with the gesture, the page starts to turn more rapidly. I want the page to turn "linearly" as the touch moves, like the Flipboard app. How can I control the translation to that it doesn't get accelerated?
The translation is accumulated. Add this to your code after the case UIGestureRecognizerStateChanged.
[recognizer setTranslation:CGPointZero inView:self.view];

Reset UIView's frame with a pan gesture without hardcoding start values

I have a UIPanGestureRecognizer that is attached to a UIView. What I am trying to do is essentially drag and drop into a bucket. If the user lets go of the UIView and it's not in the bucket, I want it to animate back to its original start location. My selector that gets called for my UIPanGestureRecognizer is:
- (void)handleDragDescriptionView:(UIPanGestureRecognizer *)gestureRecognizer {
UIView *panViewPiece = [gestureRecognizer view];
CGRect originalFrame = CGRectMake(946, 20, 58, 30);
CGPoint translation = [gestureRecognizer translationInView:panViewPiece];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan || gestureRecognizer.state == UIGestureRecognizerStateChanged) {
panViewPiece.center = CGPointMake(panViewPiece.center.x + translation.x, panViewPiece.center.y + translation.y);
[gestureRecognizer setTranslation:CGPointZero inView:panViewPiece.superview];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[UIView animateWithDuration:0.25 animations:^{
self.dragDescriptionView.frame = originalFrame;
}];
}
else {
[UIView animateWithDuration:0.25 animations:^{
self.dragDescriptionView.frame = originalFrame;
}];
}
}
I was wondering if I could do this without the originalFrame at the top of the method. I originally had
CGRect originalFrame = _dragDescriptionView.frame;
instead of the hardcoding, but it doesn't snap back. Probably because I'm updating that value as I am dragging. I don't particularly like hardcoding values, but I wasn't sure if there was a way around this. Thanks!
You really only need to track the view's original center. Make originalCenter an instance variable and only set it when the gesture begins:
#implementation MyViewController {
CGPoint _originalCenter;
}
- (void)handleDragDescriptionView:(UIPanGestureRecognizer *)gestureRecognizer {
UIView *panViewPiece = [gestureRecognizer view];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
_originalCenter = panViewPiece.center;
break;
case UIGestureRecognizerStateChanged: {
CGPoint translation = [gestureRecognizer translationInView:panViewPiece.superview];
panViewPiece.center = CGPointMake(panViewPiece.center.x + translation.x, panViewPiece.center.y + translation.y);
[gestureRecognizer setTranslation:CGPointZero inView:panViewPiece.superview];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
[UIView animateWithDuration:0.25 animations:^{
panViewPiece.center = _originalCenter;
}];
break;
}
default:
break;
}
}

Resources