Pan gesture behaving weird after default rotation and scale - ios

I am currently designing a little game as a learning project and it is basically the following, a image is rotated and scaled on viewDidLoad and another image is a direct copy of the original image.
So basically there is a image that is a little bit different from the other, the objective is to scale it back down, rotate it and move it on top of the other image with 5 pixels, 5 degrees of rotate and 5 percent scale.
I have run into an issue. I "skew" the image using the following code...
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI/2.5);
image.transform = CGAffineTransformScale(transform, 1.25, 1.25);
My pan gesture does not perform correctly after rotating the image and then scaling it 125%.
Does anyone know what could be going on here? By incorrectly I mean that it doesn't move around with my finger.. It seems to glide or go the opposite direction. Video .
My pan gesture method is below.
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:image];
//if within game field
if((image.center.x + translation.x) > 50.0 && (image.center.x + translation.x) < 255.0 && (image.center.y + translation.y) > 50.0 && (image.center.y + translation.y) < 302) {
[image setCenter:CGPointMake([image center].x + translation.x, [image center].y + translation.y)]; //move it
}
}
[gesture setTranslation:CGPointZero inView:[image superview]];
if(gesture.state == UIGestureRecognizerStateEnded) [self didWin]; // not relevant to question
Does anyone know why pan performs incorrectly after I rotate and scale my image? When I comment out those first two lines of code the pan performs correctly and moves around with the users finger.
Thanks in advance for any suggestions or help!

Rotation might have changed the frame. I used cosf and sinf function to deal with it.
handlePan and handleRotate are the callback functions, in which I can control the subview of self.view. Here, you should replace image with your own view.
static CGFloat _rotation = 0;
- (void)handlePan:(UIPanGestureRecognizer*)recognizer
{
UIImageView *image = nil;
for (UIImageView *tmp in recognizer.view.subviews) { // pick the subview
if (tmp.tag == AXIS_TAG) {
image = tmp;
}
}
CGPoint translation = [recognizer translationInView:image];
// I have found any related documents yet, but these equations do work!//////
CGFloat dx = translation.x * cosf(_rotation) - translation.y*sinf(_rotation);
CGFloat dy = translation.x * sinf(_rotation) + translation.y*cosf(_rotation);
/////////////////////////////////////////////////////////////////////////////
image.center = CGPointMake(image.center.x+dx, dy+image.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:image];
}
- (void)handleRotate:(UIRotationGestureRecognizer*)recognizer
{
UIImageView *image = nil;
for (UIImageView *tmp in recognizer.view.subviews) { // pick the subview
if (tmp.tag == AXIS_TAG) {
image = tmp;
}
}
CGFloat r = [recognizer rotation];
if ((recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged)
&& recognizer.numberOfTouches == 2) {
image.transform = CGAffineTransformMakeRotation(_rotation+r);
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
_rotation+=r; // Record the final rotation
}
}

The solution was to change the pan code just a tiny bit..
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self.view]; //CHANGED
//if within game field
if((image.center.x + translation.x) > 50.0 && (image.center.x + translation.x) < 255.0 && (image.center.y + translation.y) > 50.0 && (image.center.y + translation.y) < 302) {
[image setCenter:CGPointMake([image center].x + translation.x, [image center].y + translation.y)]; //move it
}
}
[gesture setTranslation:CGPointZero inView:self.view];
I changed in view:self.view and translationInView:self.view];

Related

Add boundaries to a UIPangesture

I have a UILabel in a subview and I have a panGesture and pinchGesture on the UILabel. As of right now I can move the UILabel crossed all views. I want this UILabel do stay within the area of the subView. How would I accomplish this?
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
CGPoint translation = [panGesture translationInView:panGesture.view.superview];
if (UIGestureRecognizerStateBegan == panGesture.state ||UIGestureRecognizerStateChanged == panGesture.state) {
panGesture.view.center = CGPointMake(panGesture.view.center.x + translation.x,
panGesture.view.center.y + translation.y);
[panGesture setTranslation:CGPointZero inView:self.view];
}
}
In this line,
CGPoint translation = [panGesture translationInView:panGesture.view.superview];
It is setting it to the superView and I am trying to set it to my subView but I can't seem to figure it out.
Here is my code to handle draggable button and restrict it to the main view boundary
I hope this code will help you
CGPoint translation = [recognizer translationInView:self.view];
CGRect recognizerFrame = recognizer.view.frame;
recognizerFrame.origin.x += translation.x;
recognizerFrame.origin.y += translation.y;
// Check if UIImageView is completely inside its superView
if (CGRectContainsRect(self.view.bounds, recognizerFrame)) {
recognizer.view.frame = recognizerFrame;
}
// Else check if UIImageView is vertically and/or horizontally outside of its
// superView. If yes, then set UImageView's frame accordingly.
// This is required so that when user pans rapidly then it provides smooth translation.
else {
// Check vertically
if (recognizerFrame.origin.y < self.view.bounds.origin.y) {
recognizerFrame.origin.y = 0;
}
else if (recognizerFrame.origin.y + recognizerFrame.size.height > self.view.bounds.size.height) {
recognizerFrame.origin.y = self.view.bounds.size.height - recognizerFrame.size.height;
}
// Check horizantally
if (recognizerFrame.origin.x < self.view.bounds.origin.x) {
recognizerFrame.origin.x = 0;
}
else if (recognizerFrame.origin.x + recognizerFrame.size.width > self.view.bounds.size.width) {
recognizerFrame.origin.x = self.view.bounds.size.width - recognizerFrame.size.width;
}
}
// Reset translation so that on next pan recognition
// we get correct translation value
[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];
}
}

Moving UIView within Parent UIView (UIPanGestureRecognizer)

I am using the following code to move a UIImageView that is present inside of a UIView.
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
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];
}
I would like to make it so that the UIView does not move outside of the parent view. At the minute the image view is able to move across the entire screen.
First get the new frame of your UIImageView and check if it is completely inside its superView using CGRectContainsRect() method. If yes, then set UImageView's frame to new frame.
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
CGRect recognizerFrame = recognizer.view.frame;
recognizerFrame.origin.x += translation.x;
recognizerFrame.origin.y += translation.y;
// Check if UIImageView is completely inside its superView
if (CGRectContainsRect(self.view.bounds, recognizerFrame)) {
recognizer.view.frame = recognizerFrame;
}
// Else check if UIImageView is vertically and/or horizontally outside of its
// superView. If yes, then set UImageView's frame accordingly.
// This is required so that when user pans rapidly then it provides smooth translation.
else {
// Check vertically
if (recognizerFrame.origin.y < self.view.bounds.origin.y) {
recognizerFrame.origin.y = 0;
}
else if (recognizerFrame.origin.y + recognizerFrame.size.height > self.view.bounds.size.height) {
recognizerFrame.origin.y = self.view.bounds.size.height - recognizerFrame.size.height;
}
// Check horizantally
if (recognizerFrame.origin.x < self.view.bounds.origin.x) {
recognizerFrame.origin.x = 0;
}
else if (recognizerFrame.origin.x + recognizerFrame.size.width > self.view.bounds.size.width) {
recognizerFrame.origin.x = self.view.bounds.size.width - recognizerFrame.size.width;
}
}
// Reset translation so that on next pan recognition
// we get correct translation value
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
Make sure that you pass bounds of superView and frame of UIImageView so that both CGRects are in same coordinate system.
Try with:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer
{
if (gesture.state==UIGestureRecognizerStateChanged || gesture.state == UIGestureRecognizerStateEnded){
UIView *superview = recognizer.view.superview;
CGSize superviewSize = superview.bounds.size;
CGSize thisSize = recognizer.view.size;
CGPoint translation = [recognizer translationInView:self.view];
CGPoint center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
CGPoint resetTranslation = CGPointMake(translation.x, translation.y);
if(center.x - thisSize.width/2 < 0)
center.x = thisSize.width/2;
else if (center.x + thisSize.width/2 > superviewSize.width)
center.x = superviewSize.width-thisSize.width/2;
else
resetTranslation.x = 0; //Only reset the horizontal translation if the view *did* translate horizontally
if(center.y - thisSize.height/2 < 0)
center.y = thisSize.height/2;
else if(center.y + thisSize.height/2 > superviewSize.height)
center.y = superviewSize.height-thisSize.height/2;
else
resetTranslation.y = 0; //Only reset the vertical translation if the view *did* translate vertically
recognizer.view.center = center;
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
}
This way it won't ever move outside the parent view bounds and it will just "stick" to the edge if you try to move it out of the bounds!
Swift version of micantox answer
let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.wasDragged(gestureRecognizer:)))
imageView.addGestureRecognizer(gesture)
imageView.isUserInteractionEnabled = true
#objc func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.changed || gestureRecognizer.state == UIGestureRecognizerState.ended {
let superview = gestureRecognizer.view?.superview
let superviewSize = superview?.bounds.size
let thisSize = gestureRecognizer.view?.frame.size
let translation = gestureRecognizer.translation(in: self.view)
var center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
var resetTranslation = CGPoint(x: translation.x, y: translation.y)
if center.x - (thisSize?.width)!/2 < 0 {
center.x = (thisSize?.width)!/2
} else if center.x + (thisSize?.width)!/2 > (superviewSize?.width)! {
center.x = (superviewSize?.width)!-(thisSize?.width)!/2
} else {
resetTranslation.x = 0 //Only reset the horizontal translation if the view *did* translate horizontally
}
if center.y - (thisSize?.height)!/2 < 0 {
center.y = (thisSize?.height)!/2
} else if center.y + (thisSize?.height)!/2 > (superviewSize?.height)! {
center.y = (superviewSize?.height)!-(thisSize?.height)!/2
} else {
resetTranslation.y = 0 //Only reset the vertical translation if the view *did* translate vertically
}
gestureRecognizer.view?.center = center
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
You'll have to set the recognizer.view.center to a new value only if its frame is inside the bounds of its parent view. Use CGRectContainsRect on recognizer.view.superview.bounds and recognizer.view.frame to verify that they are contained.
If you want to allow the image view to move outside its parent view until the center point of the view is outside the parent's bounds, you can use the convertPoint:toView method of UIView and verify that the new CGPoint is not outside your parent's bounds.
if (gesture.state==UIGestureRecognizerStateChanged){
CGPoint translation = [recognizer translationInView:self.view];
CGRect rectToCheck=CGRectMake(yourView.frame.origin.x+translation.x, yourView.frame.origin.y+translation.y, CGRectGetWidth(yourView.frame), CGRectGetHeight(yourView.frame));
if(CGRectContainsRect(self.view.bounds,rectToCheck))// check that the rect that is going to form lies within the bounds of self.view
{
yourView.frame=CGRectMake(yourView.frame.origin.x+translation.x, yourView.frame.origin.y+translation.y, CGRectGetWidth(yourView.frame), CGRectGetHeight(yourView.frame));
}
[gesture setTranslation:CGPointZero yourView]; // important to set this
}
P.S. Use the gesture state block to initiate, pan, remove. i.e. UIGestureRecognizerStateBegan
UIGestureRecognizerStateChanged
UIGestureRecognizerStateEnded

Disabling Pan Gesture if out of bounds detected

I have a UIView I am trying to move up and down the screen, however I only wish to enable it to pan so that you cannot drag the view down when it is in its normal position (0, 0)
I tried to detect when the recognizer's center is not half the height of the view, however the view is then immovable, and the center is always half the height (230 in this case).
Any ideas?
- (IBAction)panDetected:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
NSLog(#"\ncenter.y: %f\ntranslation.y: %f\n", recognizer.view.center.y, translation.y);
if (recognizer.view.center.y > ([[UIScreen mainScreen] bounds].size.height - 20)/2) {
return;
}
recognizer.view.center = CGPointMake(recognizer.view.center.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
Instead of manually computing whether the points are within the view's bounds, use CGRectContainsPoint(rect, point). This is what works for me, and I like it because it's shorter and more readable:
func handlePan(pan: UIPanGestureRecognizer) {
switch pan.state {
case .Began:
if CGRectContainsPoint(self.pannableView.frame, pan.locationInView(self.pannableView)) {
// Gesture started inside the pannable view. Do your thing.
}
}
CGPoint translation = [recognizer translationInView:self.view];
CGPoint finalpoint = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y+ translation.y);
//limit the boundary
if ((recognizer.view.frame.origin.x>0 && translation.x > 0) || (recognizer.view.frame.origin.x + recognizer.view.frame.size.width<=320 && translation.x < 0))
finalpoint.x = recognizer.view.center.x;
if ((recognizer.view.frame.origin.y>0 && translation.y > 0) || (recognizer.view.frame.origin.y + recognizer.view.frame.size.height<=self.view.frame.size.height && translation.y < 0))
finalpoint.y = recognizer.view.center.y;
//set final position
recognizer.view.center = finalpoint;
[recognizer setTranslation:CGPointZero inView:self.view];
try
CGPoint translation = [recognizer translationInView:self.view];
if (translation.x+recognizer.view.frame.origin.x<0||translation.y+recognizer.view.frame.origin.y<0)
{
return;
}
recognizer.view.center = CGPointMake(recognizer.view.center.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x, MAX(recognizer.view.center.y + translation.y, ([[UIScreen mainScreen] bounds].size.height - 20)/2));

IOS: use a gesture to calculate distance from two cgpoint

I have this code for gesture in a view
- (void)rightSwipeHandle:(UIPanGestureRecognizer*)gestureRecognizer{
CGPoint touchBegan;
CGPoint pointEnd;
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint touchBegan = [gestureRecognizer locationInView: gestureRecognizer.view];
NSLog(#"pointBegan:%#",NSStringFromCGPoint(touchBegan));
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded ||
gestureRecognizer.state == UIGestureRecognizerStateCancelled ||
gestureRecognizer.state == UIGestureRecognizerStateFailed)
{
pointEnd = [gestureRecognizer locationInView:gestureRecognizer.view];
NSLog(#"pointEnd:%#", NSStringFromCGPoint(pointEnd));
CGFloat xDist = (pointEnd.x - touchBegan.x);
CGFloat yDist = (pointEnd.y - touchBegan.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
NSLog(#"distance:%f", distance);
}
}
but it don't work fine and I don't know where is the problem...
if the swipe is from the bottom to the top, it calculate a distance and if you do the opposite it calculate a big different distance, I don'tr understand
Define the points as static, otherwise the touchBegan point will lose its value. It happens because setting the values of each point occurs in different method calls and with each one, you redefine the points at the beginning.
static CGPoint touchBegan;
static CGPoint pointEnd;

Resources