How to move uiview on circle trajectory with rotating? - iphone

just like the path in the picture
(new users aren't allowed to post images, sorry i can only give the link)
http://cdn.dropmark.com/30180/3bd0f0fc6ee77c1b6f0e05e3ea14c821f8b48a82/questiong1-1.jpg
Thanks a lot for any help

Try this on touches event delegate (if you want to rotate the view with touch event)-
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
touches = [event allTouches];
UITouch *touch = [[event allTouches] anyObject];
CGPoint Pageposition = [touch locationInView:self];
CGPoint prevPoint = [[[touches allObjects] objectAtIndex:0] previousLocationInView:self];
CGPoint curPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self];
float prevAngle = atan2(prevPoint.x, prevPoint.y);
 float curAngle= atan2(curPoint.x, curPoint.y);
float angleDifference = curAngle - prevAngle;
CGAffineTransform newTransform3 = CGAffineTransformRotate(self.transform, angleDifference);
self.transform = newTransform3;
}
use transform rotation , and tell me if it works..

Please look at my answer for this question about "rotating a label around an arbitrary point" for a more detailed explanation about using the an anchor point when rotating (ignore the keyframe animation answers since they are more complicated and not the "right tool for this job" even though the answers says so).
In short: set the anchor point to the point (in the unit-coordinate system of your views layer) to the point outside of your view and just apply a normal rotation animation.
EDIT: One thing to remember
The frame of your layer (which is also the frame of your view) is calculated using the position, bounds and anchor point. Changing the anchorPoint will change where your view appears on screen. You can counter this by re-setting the frame after changing the anchor point (this will set the position for you). Otherwise you can set the position to the point you are rotating to yourself. The Layer Geometry and Transforms (linked to in the other question) also mentions this.
The code that I provided in that answer (generalized to a UIView and Core Animation instead of UIView-animations):
#define DEGREES_TO_RADIANS(angle) (angle/180.0*M_PI)
- (void)rotateView:(UIView *)view
aroundPoint:(CGPoint)rotationPoint
duration:(NSTimeInterval)duration
degrees:(CGFloat)degrees {
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
[rotationAnimation setDuration:duration];
// Additional animation configurations here...
// The anchor point is expressed in the unit coordinate
// system ((0,0) to (1,1)) of the label. Therefore the
// x and y difference must be divided by the width and
// height of the view (divide x difference by width and
// y difference by height).
CGPoint anchorPoint = CGPointMake((rotationPoint.x - CGRectGetMinX(view.frame))/CGRectGetWidth(view.bounds),
(rotationPoint.y - CGRectGetMinY(view.frame))/CGRectGetHeight(view.bounds));
[[view layer] setAnchorPoint:anchorPoint];
[[view layer] setPosition:rotationPoint]; // change the position here to keep the frame
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(degrees), 0, 0, 1);
[rotationAnimation setToValue:[NSValue valueWithCATransform3D:rotationTransform]];
// Add the animation to the views layer
[[view layer] addAnimation:rotationAnimation
forKey:#"rotateAroundAnchorPoint"]
}

Related

Restrict image movement to grid

I'm trying to make the boxes move only in horizontal and vertical lines following the touchesMoved event.
I could easily check on every touch event if the center of the box is in the center of the current grid block and then allow a direction change. But that will require the touches to be very precise, and could also lead to bugs because of speedy fingers etc. and then miss the touch event.
What I essential would like to do is make the box move correctly in the grid, even though your touch path is not "perfect". See image below, blue lines indicates box movement, green line is the path of the touch. The box needs to follow the grid closest to the touch path, so it looks like your moving it.
How can i accomplish this?
Edit: Forgot to mention that the movement of the box should be smooth, not jump from grid to grid.
Video
Bonus info: I will also need to flicks boxes left/right up/down with touch momentum, and of course have some sort of collision detection, so boxes can't move through other boxes on the grid. Am I better off with a 2d physics engine?
Smooth animation based on hacker2007 answer:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGPoint location = [aTouch locationInView:gridContainerView];
for (UIView *subView in gridContainerView.subviews) {
if (conditionForBeingAGridBlock && CGRectContainsPoint(subview.frame, touchPoint))
{
[UIView beginAnimations:#"Dragging A DraggableView" context:nil];
self.frame = CGRectMake(0, location.y, subview.frame.size.width, subview.frame.size.height);
[UIView commitAnimations];
}
}
}
This will move the view you are dragging under your finger, you can add checks yourself to make sure that it doesn't go where it can't. But this will get your a smooth dragging animation.
Seems the problem is what you're setting the frame to in your anitamion:
Currently every time you move the square a little bit you get a new rect here:
CGRect rect = [self.grid getCellRect:self.grid.gridView x:cell.x y:cell.y];
Then in your animation you do this:
self.frame = rect;
Then you animate the box to that rect. So if you move your finger really quickly then you will get that 'skip' effect. If you change it to this:
self.frame = CGRectMake(location.x - self.bounds.size.width / 2, location.y - self.bounds.size.height / 2, self.bounds.size.width, self.bounds.size.height);
try this approach assuming your grid blocks are located in gridContainerView
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint touchPoint = [touch locationInView: gridContainerView];
for (UIView *subView in gridContainerView.subviews) {
if (conditionForBeingAGridBlock &&
CGRectContainsPoint(subview.frame, touchPoint)) {
box.frame = subview.frame;
}
}
}
}

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

Test for a pinch on UIImageView (not pinch and zoom - just test for pinch)

This isn't another one of "those" questions, don't worry.
Basically I want to test for a pinch and then run an animation. Here is the code I currently have:
// test for a pinch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if([allTouches count] == 2){
//there are two touches...
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex:0];
UITouch *secondTouch = [[allTouches allObjects] objectAtIndex:1];
CGPoint firstPoint = [firstTouch locationInView:self.superview];
CGPoint secondPoint = [secondTouch locationInView:self.superview];
int X1 = firstPoint.x;
int Y1 = firstPoint.y;
int X2 = secondPoint.x;
int Y2 = secondPoint.y;
// lets work out the distance:
// sqrt( (x2-x1)^2 + (y2-y1)^2 );
preDistance = sqrtf( (float)pow((X2-X1),2) + (float)pow((Y2-Y1),2) );
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
if([allTouches count] == 2){
//there are two touches...
UITouch *firstTouch = [[allTouches allObjects] objectAtIndex:0];
UITouch *secondTouch = [[allTouches allObjects] objectAtIndex:1];
CGPoint firstPoint = [firstTouch locationInView:self.superview];
CGPoint secondPoint = [secondTouch locationInView:self.superview];
int X1 = firstPoint.x;
int Y1 = firstPoint.y;
int X2 = secondPoint.x;
int Y2 = secondPoint.y;
// lets work out the distance:
// sqrt( (x2-x1)^2 + (y2-y1)^2 );
postDistance = sqrtf( (float)pow((X2-X1),2) + (float)pow((Y2-Y1),2) );
// lets test now
// if the post distance is LESS than the pre distance then
// there has been a pinch INWARDS
if(preDistance > postDistance){
NSLog(#"Pinch INWARDS");
// so we need to move it back to its small frame
if(CGRectEqualToRect(self.frame, largeFrame) && !CGRectIsEmpty(self.smallFrame)){
// we can go inwards
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.frame = smallFrame;
[UIView commitAnimations];
}
}
else {
NSLog(#"Pinch OUTWARDS");
// so we need to move it back to its small frame
if(!CGRectEqualToRect(self.frame, largeFrame)){
// we can go outwards
// set org frame
smallFrame = self.frame; // so we can go back later
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.frame = largeFrame;
[UIView commitAnimations];
}
}
}
}
All it does is measure the distance between the two touches if there is a multi touch, then it measures the distance again when the touches end. If the difference between the two is positive then it was an outwards pinch, and if it was negative it was an inwards pinch.
Depending on the direction of the pinch the image view will scale bigger or smaller to and from set CGRects (this is why I don't want the "normal" pinch and zoom functionality). I just want it to be a gesture to scale an image up and down again.
However, this isn't working very well... it doesn't always pick up pinches. I don't know if this is because the view its in is also detecting touches (only single pinches), but still...
Anyway, I was wondering if anyone has a better way of detecting pinches? I've currently subclassed the UIImageView to create my ImageController class which has these touch methods in them. All I need to know is a) they are touching this image and b) which direction they've pinched in.
Thanks
Check out UIPinchGestureRecognizer in the documentation. I haven't used it, but seems like it's just what you need.
Anyway, I was wondering if anyone has a better way of detecting pinches?
If you're targeting iOS 3.2 and upwards you can use gesture recognizers - there's no good reason not to. If you need to target pre 3.2, then your above approach (or varients therein, using touchesBegin, etc) is the only way, so it's just tweaking the algorithm.
If you can, I'd strongly recommend using UIPinchGestureRecognizer. It will make your life considerably easier, as it handles all the details. If you want to detect both the scale and direction of the pinch you can combine UIPinchGestureRecognizer with a UIRotationGestureRecognizer - the former will just give you the scale of a pinch, not the relative angle.
If you can live without 3.1.x devices I would recommend that you use the UIPinchGestureRecognizer. It's very simple to use, but the problem is that you're only going to be able to run it on iOS devices running 3.2 and upwards.
For some reason you are using locationInView to identify the position of the touches. Have you tried identifying the touches location in the current view (the actual image view)? Calling superview means that you will get the position of the touches in the view that contains your image view.

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

Problem with touch in cocos2D

Hey guys, i ready many many questions about that point, but i really didn't get the right one yet.
So.. this is the problem..
This is my first project with cocos2d, so sorry for that.
I have one scene called Gameplay inside that i have one Layer with the name Grid and inside of this grid, have many many Block (Layer too).
I need check when you touch one Block, i do this before with Interface Builder, so when i call touchesBegin i have the exact touch in one view. But in cocos2D, i understand you have to check the position of the objects, and not hit test then right?!
So my touchesBegin is like this:
- (void)ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: [touch view]];
//location = [[Director sharedDirector] convertCoordinate: location];
for(int i = 0; i < [arrAllBlocks count]; i++)
{
for (int j = 0; j < [[arrAllBlocks objectAtIndex:i] count]; j++)
{
Block *tempBlock = [[arrAllBlocks objectAtIndex:i] objectAtIndex:j];
CGRect mySurface = (CGRectMake(tempBlock.position.x, tempBlock.position.y, tempBlock.contentSize.width,tempBlock.contentSize.height));
if(CGRectContainsPoint(mySurface, location))
{
NSLog(#"Hit Positions %fl:%fl",location.x,location.y);
NSLog(#"Object Positions %fl:%fl",tempBlock.position.x,tempBlock.position.y);
NSLog(#"Object Color:%# hited", tempBlock.strName);
}
}
}
}
The first Problem is: This looks upsidedown! When i click in one of this blocks at the first line! I get the block at last line!! I Really dont get that! And the hit not seens perfect to me! And when i convertCoordinate this going even worst!
Someone can help me?! And sorry for bad english =/
There are two things to remember...
Cocos2D coordinate system's origin is bottom left corner.
By default, the anchor points of display elements are their centers.
So this is how I solved this problem:
Set the anchor points of the elements to be bottom left as well.
[block setAnchorPoint:ccp(0, 0)];
Use convertCoordinate in ccTouchesBegan.
-(BOOL)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint pt = [touch locationInView:[touch view]];
CGPoint ptConv = [[Director sharedDirector] convertCoordinate:pt];
}
Calculate the rectangle of the element and compare...
CGSize size = [block contentSize];
CGPoint point = [block position];
rect = CGRectMake(point.x, point.y, size.width, size.height);
if (CGRectContainsPoint(rect, ptConv))
{
// touched
}
It looks like the only step you are missing is to change the anchor point.
If you prefer not to change the anchor point, you need to change the way rectangle is calculated; adding and subtracting the half of width and height.
Hope it helps.
Remember that the coordinate system in Cocos2D is upside down from that of Cocoa. (Really, Cocoa is upside down to me, but it's just a convention).
I forget this all the time myself!