PDA

View Full Version : Best way to do multi-touch scale/rotate on iPhone




luckylefty01
Jun 29, 2008, 08:56 PM
This is the current code I'm using to do your typical iPhone rotate/scale with two fingers on a view. I did it using your fairly typical pythagorean theorem and trig combination.

What I'm wondering is if there's a better/cleaner/more efficient mathematically way to do it?

This is an example of how it could be used in program:
view.transform = CGAffineTransformWithTouches(view.transform, firstTouch, secondTouch);
[UIView commitAnimations];

The code:
CGFloat distanceBetweenPoints(CGPoint firstPoint, CGPoint secondPoint) {
CGFloat distance;

//Square difference in x
CGFloat xDifferenceSquared = pow(firstPoint.x - secondPoint.x, 2);
// NSLog(@"xDifferenceSquared: %f", xDifferenceSquared);

// Square difference in y
CGFloat yDifferenceSquared = pow(firstPoint.y - secondPoint.y, 2);
// NSLog(@"yDifferenceSquared: %f", yDifferenceSquared);

// Add and take Square root
distance = sqrt(xDifferenceSquared + yDifferenceSquared);
// NSLog(@"Distance: %f", distance);
return distance;

}

CGPoint vectorBetweenPoints(CGPoint firstPoint, CGPoint secondPoint) {
// NSLog(@"Point One: %f, %f", firstPoint.x, firstPoint.y);
// NSLog(@"Point Two: %f, %f", secondPoint.x, secondPoint.y);

CGFloat xDifference = firstPoint.x - secondPoint.x;
CGFloat yDifference = firstPoint.y - secondPoint.y;

CGPoint result = CGPointMake(xDifference, yDifference);

return result;
}

CGAffineTransform CGAffineTransformWithTouches(CGAffineTransform oldTransform,
UITouch *firstTouch,
UITouch *secondTouch) {

CGPoint firstTouchLocation = [firstTouch locationInView:nil];
CGPoint firstTouchPreviousLocaion = [firstTouch previousLocationInView:nil];
CGPoint secondTouchLocation = [secondTouch locationInView:nil];
CGPoint secondTouchPreviousLocaion = [secondTouch previousLocationInView:nil];

CGAffineTransform newTransform;

// Calculate new scale and rotation

// Get distance between points
CGFloat currentDistance = distanceBetweenPoints(firstTouchLocation,
secondTouchLocation);

CGFloat previousDistance = distanceBetweenPoints(firstTouchPreviousLocaion,
secondTouchPreviousLocaion);

// Figure new scale

CGFloat distanceRatio = currentDistance / previousDistance;
// NSLog(@"DifferenceRatio: %f, %f", distanceRatio.x, distanceRatio.y);

// Combine with current transform
newTransform = CGAffineTransformScale(oldTransform, distanceRatio, distanceRatio);


// Now figure new rotation

// Get previous angle
CGPoint previousDifference = vectorBetweenPoints(firstTouchPreviousLocaion, secondTouchPreviousLocaion);
CGFloat xDifferencePrevious = previousDifference.x;

// Use acos to get angle to avoid issue with the denominator being 0
CGFloat previousRotation = acos(xDifferencePrevious / previousDistance); // adjacent over hypotenuse
if (previousDifference.y < 0) {
// account for acos of angles in quadrants III and IV
previousRotation *= -1;
}
// NSLog(@"previousRotation: %f", previousRotation);

// Get current angle
CGPoint currentDifference = vectorBetweenPoints(firstTouchLocation, secondTouchLocation);
CGFloat xDifferenceCurrent = currentDifference.x;

// Use acos to get angle to avoid issue with the denominator being 0
CGFloat currentRotation = acos(xDifferenceCurrent / currentDistance); // adjacent over hypotenuse
if (currentDifference.y < 0) {
// account for acos of angles in quadrants III and IV
currentRotation *= -1;
}
// NSLog(@"currentRotation: %f", currentRotation);

CGFloat newAngle = currentRotation - previousRotation;

// Combine with current transform
newTransform = CGAffineTransformRotate(newTransform, newAngle);

// Return result
return newTransform;
}



sujithkrishnan
Jul 2, 2008, 02:17 AM
Hi...

For a beginner like me, it looks pretty "BIG STUFF"...
In my iPhone app, i did return YES for all orientation...

if i take that view in portrait mode it will fix perfectly to that view...
But if i rotate to landscape mode, view is rotating, but bounds are same as previous.. means in lanscape mode, i can see the view rotated, but occupying half the width, and more hieght...

Help....

kpua
Jul 2, 2008, 02:40 AM
I don't know if there's any existing functionality for doing this, but I think you've got the right idea.

For code brevity's sake, you can use hypot() to calculate the hypotenuse of a triangle and you can use atan2(), which will put the angle in the proper quadrant for you. (These are common Unix functions, but I don't know for sure if they're on the phone.)

luckylefty01
Jul 2, 2008, 02:33 PM
Thanks Kupa, I hadn't seen those before. They're both in math.h so they should definitely be on the phone.

jagatnibas
Sep 15, 2008, 12:43 PM
Hi,

I am trying to modify your code and try rotate and stretch in one direction. How can i accomplish this ? my code is as follows

CGAffineTransform CGAffineTransformWithTouches(CGAffineTransform oldTransform,
/*UITouch *firstTouch,*/
UITouch *secondTouch) {

CGPoint secondTouchLocation = [secondTouch locationInView:nil];
CGPoint secondTouchPreviousLocaion = [secondTouch previousLocationInView:nil];

CGAffineTransform newTransform;

// Calculate new scale and rotation

// Get distance between points
CGFloat currentDistance = distanceBetweenPoints(secondTouchLocation, secondTouchPreviousLocaion);

// Figure new scale

CGFloat distanceRatio = currentDistance ;/// previousDistance;

// Combine with current transform
newTransform = CGAffineTransformScale(oldTransform, distanceRatio, distanceRatio);

CGPoint currentDifference = vectorBetweenPoints(secondTouchLocation, secondTouchPreviousLocaion);
CGFloat xDifferenceCurrent = currentDifference.x;

// Use acos to get angle to avoid issue with the denominator being 0
CGFloat currentRotation = acos(xDifferenceCurrent / currentDistance); // adjacent over hypotenuse
if (currentDifference.y < 0) {
// account for acos of angles in quadrants III and IV
currentRotation *= -1;
}

//CGFloat newAngle = currentRotation - previousRotation;

// Combine with current transform
newTransform = CGAffineTransformRotate(newTransform, currentRotation);
// newTransform = CGAffineTransformMakeRotation(currentRotation);

// Return result
return newTransform;
}

the thing is, it is stretching but when it rotates, it starts stretching vertically, which i dont want. i want only stretched towards the direction where touch moves.

basically what does a, b, c, d and tx ty in cgaffine struct mean ?

please gimme some ideas

Jagat

narut
Jan 7, 2009, 12:59 AM
I know this thread is a bit old, but just in case it might be useful for someone looking for the easy way to rotate view (rotate only, no zoom) with two fingers.

Here's my code.
UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];
UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1];
CGPoint previousPoint1 = [touch1 previousLocationInView:nil];
CGPoint previousPoint2 = [touch2 previousLocationInView:nil];
CGFloat previousAngle = atan2 (previousPoint2.y - previousPoint1.y, previousPoint2.x - previousPoint1.x);

CGPoint currentPoint1 = [touch1 locationInView:nil];
CGPoint currentPoint2 = [touch2 locationInView:nil];
CGFloat currentAngle = atan2 (currentPoint2.y - currentPoint1.y, currentPoint2.x - currentPoint1.x);

transform = CGAffineTransformRotate(transform, currentAngle - previousAngle);
self.view.transform = transform;

Put this under touchesMoved method in your view controller.
Cheers ;)

allbrokeup
Jan 15, 2009, 05:29 AM
I whacked that code into my TouchesMoved method, but im getting "transform undeclared. First use in this function" error message. I have never had to "Declare" any of my dozens of Translation Transforms before...

Do I have to put "transform" anywhere else?


Edit: I simply put "CGAffineTransform transform = ...." before "transform". It worked, but my View (A timetable) I want to flip with two fingers keeps vanishing? Do I have to import "Math.h"? And If so, how do I do it?

Edit 2: A single swipe crashes the app in the simulator. I held Option to do a Two Fingered Rotate but it won't work still.

mcs07
May 2, 2009, 05:51 PM
I've implemented this code in a a view with an UIImageView as a subview. With the image in place it becomes really slow in the simulator, so I guess it will be even worse on the device.

Is there a better way to implement this with images? Placing an image in a scroll view almost gives you the same functionality, but I understand it doesn't allow you to rotate?

Edit: Maybe putting the image in a scroll view then using narut's rotate only code to rotate the scroll view will work better. I'll try that when I have some time.

devang.pandya
May 20, 2009, 02:15 AM
Hi,
i need to move and rotate(simultaneously) an imageview with two touches,which will make it feel like a real object. i am able to both, but when i try to do it simultaneousl i get horrible output. I think i will have to figure out the angle and then decide whether to move or rotate it, but i am not able to do it. If any body has any idea,kindly post it.
Thanks for reply