How to move a UIImageView after applying CGAffineTransformRotate to it? - iphone

I'm building an iPhone app. I have a UIView that contains a set of UIImageView subclass objects. The user can drag and rotate the image views via touch. I'm having trouble moving the image view after it has been rotated.
To rotate an image view, I apply a transform rotation, which works fine. It looks like this:
CGAffineTransform trans = self.transform;
self.transform = CGAffineTransformRotate(trans, delta);
The problem comes later when the user tries to move the element via touch. In touchesBegan:WithEvent:, I save the start point in a class variable, startLocation:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
// Retrieve the touch point
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
}
In touchesMoved:withEvent:, I had the following code, which works well enough if there is no rotation transform on the image view:
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt = [[touches anyObject] locationInView:self];
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(self.center.x + dx, self.center.y + dy);
self.center = newCenter;
}
But if there is a rotation transform on the image view, then the image view thrashes about the screen on each touchesMoved event and soon disappears. In the debugger, I observed that the value of pt became monstrous. It occurred to me that I needed to transform that point, which I did, like so:
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt = [[touches anyObject] locationInView:self];
if (!CGAffineTransformIsIdentity(self.transform)) {
pt = CGPointApplyAffineTransform(pt, self.transform);
}
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(self.center.x + dx, self.center.y + dy);
}
This worked much better. I can drag the image about the screen now. But the very first movement causes the image to jolt once in one direction or another, depending on the angle of rotation in the transform and the dimensions of the image view.
How can I move the image view without having the initial jolt?
Why is it that I do not need to transform startLocation (the point I capture when touches began)?

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)_event {
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)_event {
CGPoint pt = [[touches anyObject] previousLocationInView:self];
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(self.center.x + dx, self.center.y + dy);
self.center = newCenter;
}
- (void)setRotation:(float)rotation {
self.transform = CGAffineTransformRotate(self.transform, degreesToRadians(rotation));
}

It seems you need to convert the coordinates from the main not-rotated object to the rotated-view system.
Take a look at the UIView method "convertPoint:toView:":
convertPoint:toView:
Converts a point from the receiver’s coordinate system to that of the specified view.
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view
Parameters
point
A point specified in the local coordinate system (bounds) of the receiver.
view
The view into whose coordinate system point is to be converted. If view is nil, this method instead converts to window base coordinates. Otherwise, both view and the receiver must belong to the same UIWindow object.
Return Value
The point converted to the coordinate system of view.
Update:
In response to the comments:
You have to get the "real" coordinates of the finger touch (in the non-rotated system), then when the finger moves you always have new coordinates in the main non-rotated view: and they are the point that you have to convert in the rotated view parent of the view that you are moving.
If A is a mainView 320x480 pixel
and B is a subView 320x480 pixel centered in A,
and C is a subView in B at position 170,240 (+10,+0 of the screen center)
and you rotate B of 90 degrees clockwise
then C is still in 170,240 in B
But you see it in 160,250 on the screen,
and if now the user want to move it +20 to the right the user moves the finger +20 in the screen coordinates, not in the B view coordinates,
so the user would like to see it in 180,250 of the screen,
that means you need to convert this point in the B coordinates system...
So it's a bit easier, you just need to get the screen coordinates of the finger when the user moves it and just convert it in rotated-view coordinates (B)...

Related

iOS: Single Finger rotation and translation of an image

Currently i am following this Link to perform Single finger rotation on an image and its working fine.But i am wondering after performing rotation, how to perform translation operation if i want to move the image from one postion of screen to any other position.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// We can look at any touch object since we know we
// have only 1. If there were more than 1 then
// touchesBegan:withEvent: would have failed the recognizer.
UITouch *touch = [touches anyObject];
// A tap can have slight movement, but we're not interested
// in a tap. We want more movement. So if a tap is detected
// fail the recognizer.
if ([self state] == UIGestureRecognizerStatePossible) {
[self setState:UIGestureRecognizerStateBegan];
} else {
[self setState:UIGestureRecognizerStateChanged];
}
// To rotate with one finger, we simulate a second finger.
// The second figure is on the opposite side of the virtual
// circle that represents the rotation gesture.
UIView *view = [self view];
CGPoint center = CGPointMake(CGRectGetMidX([view bounds]), CGRectGetMidY([view bounds]));
CGPoint currentTouchPoint = [touch locationInView:view];
CGPoint previousTouchPoint = [touch previousLocationInView:view];
// use the movement of the touch to decide
// how much to rotate the carousel
CGPoint line2Start = currentTouchPoint;
CGPoint line1Start = previousTouchPoint;
CGPoint line2End = CGPointMake(center.x + (center.x - line2Start.x), center.y + (center.y - line2Start.y));
CGPoint line1End = CGPointMake(center.x + (center.x - line1Start.x), center.y + (center.y - line1Start.y));
//////
// Calculate the angle in radians.
// (From: http://iphonedevelopment.blogspot.com/2009/12/better-two-finger-rotate-gesture.html )
CGFloat a = line1End.x - line1Start.x;
CGFloat b = line1End.y - line1Start.y;
CGFloat c = line2End.x - line2Start.x;
CGFloat d = line2End.y - line2Start.y;
CGFloat line1Slope = (line1End.y - line1Start.y) / (line1End.x - line1Start.x);
CGFloat line2Slope = (line2End.y - line2Start.y) / (line2End.x - line2Start.x);
CGFloat degs = acosf(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
CGFloat angleInRadians = (line2Slope > line1Slope) ? degs : -degs;
//////
[self setRotation:angleInRadians];
}
I am not sure that my answer will help you, but I fear that you cannot do "one-touch" rotation and "one-touch" translation at the same time. Indeed, your code always interprets a single touch as for rotation, by "simulating" the presence of a second finger touching the display.
What you could do is trying and differentiate two single-finger gestures so to associate one with rotation and the other with translation. For example, the gesture for translation could be a "long touch" (i.e., you touch and keep touching for some time, then you move the object).
Hope this helps, anyway...

Applying Zoom Effect In cocos2D gaming environment?

I'm working on a game with cocos2D game engine and make load all the sprites while it load the level, now as because some of sprites (obstacles) are taller than 320 pixel, thus it seems difficult to check them out. So for the convenience sake I want to apply ZOOM IN and ZOOM out effect, which minimizes entire level's all sprites at once, and in zoom out case these will resided to there old position.
Can I achieve this?
If yes, then how?
Please tell about pinch zoom also.
Zooming, is fairly simple, simply set the scale property of your main game layer... but there are a few catches.
When you scale the layer, it will shift the position of the layer. It won't automatically zoom towards the center of what you're currently looking at. If you have any type of scrolling in your game, you'll need to account for this.
To do this, set the anchorPoint of your layer to ccp(0.0f, 0.0f), and then calculate how much your layer has shifted, and reposition it accordingly.
- (void) scale:(CGFloat) newScale scaleCenter:(CGPoint) scaleCenter {
// scaleCenter is the point to zoom to..
// If you are doing a pinch zoom, this should be the center of your pinch.
// Get the original center point.
CGPoint oldCenterPoint = ccp(scaleCenter.x * yourLayer.scale, scaleCenter.y * yourLayer.scale);
// Set the scale.
yourLayer.scale = newScale;
// Get the new center point.
CGPoint newCenterPoint = ccp(scaleCenter.x * yourLayer.scale, scaleCenter.y * yourLayer.scale);
// Then calculate the delta.
CGPoint centerPointDelta = ccpSub(oldCenterPoint, newCenterPoint);
// Now adjust your layer by the delta.
yourLayer.position = ccpAdd(yourLayer.position, centerPointDelta);
}
Pinch zoom is easier... just detect the touchesMoved, and then call your scaling routine.
- (void) ccTouchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
// Examine allTouches instead of just touches. Touches tracks only the touch that is currently moving...
// But stationary touches still trigger a multi-touch gesture.
NSArray* allTouches = [[event allTouches] allObjects];
if ([allTouches count] == 2) {
// Get two of the touches to handle the zoom
UITouch* touchOne = [allTouches objectAtIndex:0];
UITouch* touchTwo = [allTouches objectAtIndex:1];
// Get the touches and previous touches.
CGPoint touchLocationOne = [touchOne locationInView: [touchOne view]];
CGPoint touchLocationTwo = [touchTwo locationInView: [touchTwo view]];
CGPoint previousLocationOne = [touchOne previousLocationInView: [touchOne view]];
CGPoint previousLocationTwo = [touchTwo previousLocationInView: [touchTwo view]];
// Get the distance for the current and previous touches.
CGFloat currentDistance = sqrt(
pow(touchLocationOne.x - touchLocationTwo.x, 2.0f) +
pow(touchLocationOne.y - touchLocationTwo.y, 2.0f));
CGFloat previousDistance = sqrt(
pow(previousLocationOne.x - previousLocationTwo.x, 2.0f) +
pow(previousLocationOne.y - previousLocationTwo.y, 2.0f));
// Get the delta of the distances.
CGFloat distanceDelta = currentDistance - previousDistance;
// Next, position the camera to the middle of the pinch.
// Get the middle position of the pinch.
CGPoint pinchCenter = ccpMidpoint(touchLocationOne, touchLocationTwo);
// Then, convert the screen position to node space... use your game layer to do this.
pinchCenter = [yourLayer convertToNodeSpace:pinchCenter];
// Finally, call the scale method to scale by the distanceDelta, pass in the pinch center as well.
// Also, multiply the delta by PINCH_ZOOM_MULTIPLIER to slow down the scale speed.
// A PINCH_ZOOM_MULTIPLIER of 0.005f works for me, but experiment to find one that you like.
[self scale:yourlayer.scale - (distanceDelta * PINCH_ZOOM_MULTIPLIER)
scaleCenter:pinchCenter];
}
}
If all the sprites have the same parent you can just scale their parent and they will be scaled with it, keeping their coordinates relative to the parent.
this code scale my Layer by 2 to specific location
[layer setScale:2];
layer.position=ccp(240/2+40,160*1.5);
double dx=(touchLocation.x*2-240);
double dy=(touchLocation.y*2-160);
layer.position=ccp(inGamePlay.position.x-dx,inGamePlay.position.y-dy);
My code and it works better than other ones:
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray* allTouches = [[event allTouches] allObjects];
CCLayer *gameField = (CCLayer *)[self getChildByTag:TAG_GAMEFIELD];
if (allTouches.count == 2) {
UIView *v = [[CCDirector sharedDirector] view];
UITouch *tOne = [allTouches objectAtIndex:0];
UITouch *tTwo = [allTouches objectAtIndex:1];
CGPoint firstTouch = [tOne locationInView:v];
CGPoint secondTouch = [tTwo locationInView:v];
CGPoint oldFirstTouch = [tOne previousLocationInView:v];
CGPoint oldSecondTouch = [tTwo previousLocationInView:v];
float oldPinchDistance = ccpDistance(oldFirstTouch, oldSecondTouch);
float newPinchDistance = ccpDistance(firstTouch, secondTouch);
float distanceDelta = newPinchDistance - oldPinchDistance;
NSLog(#"%f", distanceDelta);
CGPoint pinchCenter = ccpMidpoint(firstTouch, secondTouch);
pinchCenter = [gameField convertToNodeSpace:pinchCenter];
gameField.scale = gameField.scale - distanceDelta / 100;
if (gameField.scale < 0) {
gameField.scale = 0;
}
}
}

Calculate angle for rotation in Pie Chart

I want to rotate the image around its center point.The problem i am facing is i need to get the angle to calculate in touch moved event (i dont want to use multi touch).I am current using the below code
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
NSArray *allTouches = [touches allObjects];
gestureStartPoint = gestureMovedPoint;//i am getting the gestureStartPoint on touch began event
gestureMovedPoint = [[allTouches objectAtIndex:0] locationInView:[self superview]];
NSLog(#"gestureMovedPoint = %#",NSStringFromCGPoint(gestureMovedPoint));
}
CGFloat previousAngle = [self angleBetweenPoints:gestureStartPoint second11:gestureMovedPoint]; // atan2(gestureMovedPoint.y - gestureStartPoint.y, gestureMovedPoint.x - gestureStartPoint.x) * 180 / M_PI;
CGFloat currentAngle =atan2(self.transform.b, self.transform.a);//atan2(gestureMovedPoint.y - gestureStartPoint.y,gestureMovedPoint.x - gestureStartPoint.x) * 180 / M_PI;
CGFloat angleToRotate = currentAngle - previousAngle;
float xpoint = (((atan2((gestureMovedPoint.x - gestureStartPoint.x) , (gestureMovedPoint.y - gestureStartPoint.y)))*180)/M_PI);
CGAffineTransform transform = CGAffineTransformMakeRotation(angleToRotate-100);
self.transform = transform;
Kindly help me find the solution as i am stuck here and need to complete this application very soon as there is a dead line.
Thanks in advance
Glad I remember triginometry
-(void)degreesToRotateObjectWithPosition:(CGPoint)objPos andTouchPoint:(CGPoint)touchPoint{
float dX = touchPoint.x-objPos.x; // distance along X
float dY = touchPoint.y-objPos.y; // distance along Y
float radians = atan2(dY, dX); // tan = opp / adj
//Now we have to convert radians to degrees:
float degrees = radians*M_PI/360;
return degrees;
}
Once you have your nice method, just do this in the touch event method. (I forgot what it's called...)
CGAffineTransform current = view.transform;
[view setTransform:CGAffineTransformRotate(current, [self degreesTorotateObjectWithPosition:view.frame.origin andTouchPoint:[touch locationInView:parentView]]
//Note: parentView = The view that your object to rotate is sitting in.
This is pretty much all the code that you'll need.The math is right, but I'm not sure about the setTransform stuff. I'm at school writing this in a browser. You should be able to figure it out from here.
Good luck,
Aurum Aquila
Have to think at this. But I will prefer rotating the view with two touches. It will be much simpler.
I did struggle a bit with how to get a touch driven rotation, even more so because I want 100% understanding of the code I am using. So I ended up, after many failed attempts, with this:
- (CGFloat) pointToAngleFromCenter: (CGPoint) point {
// transform point to a self.center'ed origin based coordinate system
point.x = point.x - self.center.x ;
// ditto for y, but compensate for y going downwards to y going upwards
point.y = self.center.y - point.y ;
return ::atan2(point.y, point.x) ;
}
If anyone has a better name for this method, I'm all ears.
What it does is that it takes a point in parent view coordinates, remaps it relative to the center of the view (which is in parent view coordinate), and computes the angle between this remapped point and the axis [0X]. To do so, it normalizes y to the normal mathematical coordinates (y goes up when its value increases, not down), hence self.center.y - point.y and not the opposite.
Finally, in touchesMoved:
- (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event {
UITouch * touch = [touches anyObject] ;
CGFloat currA = [self pointToAngleFromCenter:[touch locationInView:self.superview]] ;
CGFloat prevA = [self pointToAngleFromCenter:[touch previousLocationInView:self.superview]] ;
// the current value of the rotation angle
CGFloat tranA = ::atan2(self.transform.b, self.transform.a) ;
// the angle difference between last touch and the current touch
CGFloat diffA = currA - prevA ;
// the new angle resulting from applying the difference
CGFloat angle = tranA - diffA ;
CGAffineTransform t = ::CGAffineTransformMakeRotation(angle) ;
self.transform = t ;
[self setNeedsDisplay] ;
}

Set center point of UIView after CGAffineTransformMakeRotation

I have a UIView that I want the user to be able to drag around the screen.
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt = [[touches anyObject] locationInView:self];
CGPoint center = self.center;
CGFloat adjustmentX = pt.x - startLocation.x;
CGFloat adjustmentY = pt.y - startLocation.y;
center.x += adjustmentX;
center.y += adjustmentY;
[self setCenter:center];
}
works perfectly until I apply a transform to the view as follows:
self.transform = CGAffineTransformMakeRotation(....);
However, once the transform has been applied, dragging the UIView results in the view being moved in a exponential spiral effect.
I'm assuming I have to make an correction for the rotation in the touchesMoved method, but so far all attempts have eluded me.
You need to translate your transform to your new coordinates because a transform other than the identity is assigned to your view and moving its center alters the transform's results.
Try this instead of altering your view's center
self.transform = CGAffineTransformTranslate(self.transform, adjustmentX, adjustmentY);

Rotate UIView using UITouch

I have a question on how I should approach rotating a UIView with touch based events.
Quite simply, I want to rotate a UIView around an anchorpoint, depending on where I touch on and off within that view. Imagine a volume knob on a hifi, which you turn with one finger.
I've built a rotation method using CAKeyframeAnimation which carries out a transform.rotation.z, however what I am not sure is if (a) I should use that in conjunction with touch methods, and (b) how I can get the coordinate/angle of the touch in relation to the underlying UIView, and subsequently turn it to point to the touch.
I hope I am making sense… Can anyone make any suggestions?
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint newLocationPoint = [[touches anyObject] locationInView:self.superview];
int x = self.superview.center.x;
int y = self.superview.center.y;
float dx = newLocationPoint.x - x;
float dy = newLocationPoint.y - y;
double angle = atan2(-dx,dy);
self.layer.position = self.superview.center;
self.layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
NSLog(#"%f,%f ", dx,dy);
}
Put this in your UIView Subclass and Voila!
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint Location = [[touches anyObject] locationInView:self];
int x = ImageView.center.x;
int y = ImageView.center.y;
float dx = Location.x - x;
float dy = Location.y - y;
double a = atan2(-dx,dy);
ImageView.layer.position = diagramImageView.center;
ImageView.layer.transform = CATransform3DMakeRotation(a, 0, 0, 1);
}