How to animate rotated and moved UIView to its original position? - ios

I've got an UIImageView with a pan gesture recognizer, which I move and rotate based on user action.
When user lifts the finger, I want it to be animated back to its original position, here's my code:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
startX = recognizer.view.center.x;
startY = recognizer.view.center.y;
startRotation = atan2(recognizer.view.transform.b, recognizer.view.transform.a);
NSLog(#"Start Position %f %f %f", startX, startY, startRotation);
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
float distance = (startX - recognizer.view.center.x) / DISTANCE_TO_ACCEPT;
CGPoint center = recognizer.view.center;
// animate back
[UIView animateWithDuration:0.5 animations:^{
NSLog(#"Position %f %f %f", recognizer.view.center.x, recognizer.view.center.y, atan2(recognizer.view.transform.b, recognizer.view.transform.a));
NSLog(#"Destination %f %f %f", startX, startY, startRotation);
NSLog(#"Translation %f %f %f", startX - center.x, startY - center.y, (startRotation - atan2(recognizer.view.transform.b, recognizer.view.transform.a)));
recognizer.view.transform = CGAffineTransformRotate(CGAffineTransformTranslate(recognizer.view.transform, startX - center.x, startY - center.y), (CGFloat) (startRotation - atan2(recognizer.view.transform.b, recognizer.view.transform.a)));
}];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
// move image
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];
// rotate image
float distance = (startX - recognizer.view.center.x) / DISTANCE_TO_ACCEPT;
// cap distance
if (distance > 1) {
distance = 1;
} else if (distance < -1) {
distance = -1;
}
double rotation = 15 - startRotation;
rotation = rotation * distance;
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, (CGFloat) ((rotation * M_PI / 180) - atan2(recognizer.view.transform.b, recognizer.view.transform.a)));
}
}
But it never goes to the original position. It always has some kind of offset. Further movement of the image view only makes it worse.
It looks like the position is not modified during the back animation, because when I pan the image again, the start position and rotation is equal to the position and rotation it had at the end of previous movement [even though on screen the image position and rotation has changed].
What am I doing wrong here?
Thanks

Try something like this in UIGestureRecognizerStateEnded state:
recognizer.view.transform = CGAffineTransformIdentity;
recognizer.view.center = CGPointMake(startX,startY);

Related

UITapGesture to drag view but limiting the bounds of which it can be dragged

I currently have 2 circles. One big circle and one little circle. The little circle has a tap gesture recognizer that allows it to be dragged by the user. I would like the little circle's center to go no further than the big circle's radius. I have 4 auto layout constraints on the inner circle. 1 for fixed width, 1 for fixed height, 1 for distance from center for x, and 1 for distance from center for y. Here is how I am going about this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.view];
CGFloat x = recognizer.view.center.x + translation.x;
CGFloat y = recognizer.view.center.y + translation.y;
CGPoint desiredPoint = CGPointMake(x, y);
//check if point the user is trying to get to is outside the radius of the outer circle
//if it is, set the center of the inner circle to the right position at the distance of the radius and with the same angle
if ([self distanceBetweenStartPoint:self.outerCircleView.center endPoint:desiredPoint] > self.outerCircleRadius) {
CGFloat angle = [self angleBetweenStartPoint:self.outerCircleView.center endPoint:actualPosition];
desiredPoint = [self findPointFromRadius:self.outerCircleRadius startPoint:self.outerCircleView.center angle:angle];
}
//adjust the constraints to move the inner circle
self.innerCircleCenterXConstraint.constant += actualPosition.x - recognizer.view.center.x;
self.innerCircleCenterYConstraint.constant += actualPosition.y - recognizer.view.center.y;
[recognizer setTranslation:CGPointMake(0.0, 0.0) inView:self.view];
}
}
- (CGFloat)distanceBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGFloat xDif = endPoint.x - startPoint.x;
CGFloat yDif = endPoint.y - startPoint.y;
//pythagorean theorem
return sqrt((xDif * xDif) + (yDif * yDif));
}
- (CGPoint)findPointFromRadius:(CGFloat)radius startPoint:(CGPoint)startPoint angle:(CGFloat)angle {
CGFloat x = radius * cos(angle) + startPoint.x;
CGFloat y = radius * sin(angle) + startPoint.y;
return CGPointMake(x, y);
}
- (CGFloat)angleBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
return atan2f(originPoint.y, originPoint.x);
}
This works almost perfectly. The problem is I try to find the percentage that the user moved towards the outside of the circle. So I use the distanceBetweenStartPoint(center of outer circle) endPoint(center of inner circle) method and divide that by the radius of the outer circle. This should give me a value of 1 when the circle has been dragged as far to one side as it can go. Unfortunately I am getting values like 0.9994324 or 1.000923. What could be causing this? Thanks for any insight!

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];

UIView flipping by UIPanGestureRecognizer

I'm trying to use UIPanGestureRecognizer to flip an UIView. I'm using below code to handle flipping the view,
- (void)handlePan:(UIPanGestureRecognizer*)gesture{
CGPoint translation = [gesture translationInView:self.view];
NSLog(#"PAN X VALUE %f", translation.x );
double percentageOfWidth = translation.x / (gesture.view.frame.size.width / 2);
float angle = (percentageOfWidth * 100) * M_PI_2 / 180.0f;
CALayer *layer = testView.layer;
CATransform3D flipTransform = CATransform3DIdentity;
flipTransform.m34 = -0.002f;
flipTransform = CATransform3DRotate(flipTransform, angle, 0.0f, 1.0f, 0.0f);
layer.transform = flipTransform;
}
My problem is when i pan, sometimes there are some quick jumpings happen, I believe thats because translation.x(PAN X VALUE) value jumps from few points ahead, In my case i need it to be very smooth.
Any help greatly appreciated.
Thanks in advance.
You can use the gestureRecognizerShouldBegin method, which u can limit the UIPanGestureRecognizer sensitivity.
Example:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint translation = [panGestureRecognizer translationInView:self.view];
return fabs(translation.y) < fabs(translation.x);
}
Here's how I solved this for a cube rotation - just take the amount dragged and divide:
- (void)panHandle:(UIPanGestureRecognizer*)recognizer;
{
if ([recognizer state] == UIGestureRecognizerStateBegan)
{
CGPoint translation = [recognizer translationInView:[self superview]];
_startingX = translation.x;
}
else if ([recognizer state] == UIGestureRecognizerStateChanged)
{
CGPoint translation = [recognizer translationInView:[self superview]];
CGFloat angle = -(_startingX - translation.x) / 4;
//Now do translation with angle
_transitionContainer.layer.sublayerTransform = [self->_currentMetrics asTransformWithAngle:angle];
}
else if ([recognizer state] == UIGestureRecognizerStateEnded)
{
[self transitionOrReturnToOrigin];
}
}

Lock direction of UIPanGestureRecognizer

I have a UIPanGestureRecognizer that I use to pull up a view. the view starts at x = 160, y= 817 and when it is flicked up, I want it to go to 160,284. And When it is flicked back down I want it to automatically go back to the starting point. Here is my code so far:
-(IBAction) dragMe: (UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
recognizer.view.center = CGPointMake(recognizer.view.center.x, + recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view.superview];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:recognizer.view.superview];
CGFloat magnitude = sqrtf((velocity.y * velocity.y));
CGFloat slideMult = magnitude / 100;
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(recognizer.view.center.x,
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 160), recognizer.view.superview.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 284), recognizer.view.superview.bounds.size.height);
[UIView animateWithDuration:slideFactor*1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];
}
}
I don't want the user to be able to move it at all horizontally, just vertically.

iOS UIPinchGestureRecognizer getting offset with unknown source at the UIGestureRecognizerStateEnded state

I have a UIView with custom drawRect() method, used for some drawing.
I use UIPinchGestureRecognizer to achieve zooming effect.
This is action method:
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
{
//NSLog(#"Pinch gesture recognized");
CGPoint touchOrigin = [recognizer locationInView:self];
_currentScaleOrigin = touchOrigin;
if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSLog(#"%16# x = %f, y = %f", #"Pinch end:", touchOrigin.x, touchOrigin.y);
return;
}
if (recognizer.state == UIGestureRecognizerStateChanged)
{
//NSLog(#"scale = %f", recognizer.scale);
NSLog(#"%16# x = %f, y = %f", #"Pinch origin:", touchOrigin.x, touchOrigin.y);
// scale has at start a value of 1.0 and increases as fingers moves away from each other
_currentScaleLevel += recognizer.scale - 1;
[self setNeedsDisplay]; // call drawRect for redrawing at current scale level
}
recognizer.scale = 1;
}
When receiving UIGestureRecognizerStateEnded message i get some strange offset to coordinates:
Pinch origin: x = 358.000000, y = 630.000000
Pinch origin: x = 355.000000, y = 627.000000
Pinch origin: x = 353.000000, y = 625.000000
Pinch origin: x = 351.000000, y = 624.000000
Pinch origin: x = 351.000000, y = 623.000000
Pinch origin: x = 350.000000, y = 622.000000
Pinch origin: x = 349.000000, y = 622.000000
Pinch origin: x = 349.000000, y = 622.000000
Pinch end: x = 315.000000, y = 750.000000
... and translation for free :) which i dont need.
I dont know from where translation comes from.
How to disable this translation?
Solved it.
When receiving UIGestureRecognizerStateEnded i was assigning a point of last of two fingers to my _currentScaleOrigin which is for performing scaling transformation.
I didnt know that UIPinchGestureRecognizer returns a CGPoint of last finger position on screen.
Here is whole method code:
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
{
#if 1
static CGPoint point_0;
static CGPoint point_1;
if ([recognizer numberOfTouches] > 1)
{
point_0 = [recognizer locationOfTouch:0 inView:self];
point_1 = [recognizer locationOfTouch:1 inView:self];
NSLog(#"pinch loc 0: (%f, %f)", point_0.x , point_0.y);
NSLog(#"pinch loc 1: (%f, %f)", point_1.x , point_1.y);
} else if ([recognizer numberOfTouches] == 1) {
point_0 = [recognizer locationOfTouch:0 inView:self];
NSLog(#"pinch loc 0: (%f, %f)", point_0.x , point_0.y);
}
#endif
switch (recognizer.state)
{
case UIGestureRecognizerStateBegan:
{
CGPoint pointBegin = [recognizer locationInView:self];
NSLog(#"%16# x = %f, y = %f", #"Pinch start:", pointBegin.x, pointBegin.y);
} break;
case UIGestureRecognizerStateChanged:
{
_currentScaleOrigin = [recognizer locationInView:self];
//NSLog(#"scale = %f", recognizer.scale);
NSLog(#"%16# x = %f, y = %f", #"Pinch origin:", _currentScaleOrigin.x, _currentScaleOrigin.y);
// scale has at start a value of 1.0 and increases as fingers moves away from each other
_currentScaleLevel += recognizer.scale - 1;
// call drawRect for redrawing at current scale level
[self setNeedsDisplay];
} break;
case UIGestureRecognizerStateEnded:
{
CGPoint pointEnded = [recognizer locationInView:self];
NSLog(#"%16# x = %f, y = %f", #"Pinch end:", pointEnded.x, pointEnded.y);
//return;
} break;
default :
{
NSLog(#"other state");
}
}
recognizer.scale = 1;
}

Resources