Acceleration, moving items - ios

I'm now developing a game that uses acceleration to play. I found out how to make my item move, but not to change its 'origin', or more precisely, the origin for acceleration calculation:
In fact, my image moves, and its center is defined like this:
imageView.center = CGPointMake(230, 240);
As you can see, I use landscape mode. But, I want that my image moves "progressively". What i mean by progressively is like in the game Lane Splitter:
You can see that the bike moves, and for example, when he's completely on the left side, the man can orient his iPad horizontally, but the bike doesn't go back in the middle of the screen. I don't know how to do that, because when I try a solution, my image moves, but gets back to the center as soon as my iPhone is horizontal. I understand why, but I don't know how to correct this.
This is my current code:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
int i = 0;
float current;
if (i == 0)
{
imageView.center = CGPointMake(230, 240);
current = 240;
i++;
}
//try to modify the origin of acceleration
imageView.center = CGPointMake(230, current - (acceleration.y*200));
current = imageView.center.y;
}

The problem is that i is a local variable. Your code is equivalent to
imageView.center = CGPointMake(230, 240);
float current = 240;
imageView.center = CGPointMake(230, current - (acceleration.y*200));
[imageView center];
Instead, try something like this (assuming your image view is at the right location on startup):
CGPoint current = imageView.center;
current.y -= acceleration.y*200;
imageView.center = current;
Also bear in mind that acceleration.y is in the device coordinate space; you'll need to compensate for interface rotation if your UI supports multiple orientations.

Related

New center point for transformed UIView with new frame

I been working on an iOS app which should display indoor blueprints. You should be able to switch between floors and each floor image is controlled by gesture recognisers to handle pan, rotate and scale.
I have been using this example for the gesture recognisers: https://github.com/GreenvilleCocoa/UIGestures/blob/master/UIGestures/RPSimultaneousViewController.m
So now to the problem. Whenever the user switch floor I want to keep the transformation of the image as well as the corresponding center lat/lng. However, the new image can have another rotation offset and aspect ratio.
I have been able to update the new frame of the image with the new size and update the transform with the new rotation offset and verified it. It is when I try to calculate the new center point I can not get it to work. The following code is how I currently do it and it works as long as the view is not rotated:
-(void)changeFromFloor:(int)oldFloorNr toFloor:(int)newFloorNr
{
CGPoint centerPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
// This is the old non transformed center point.
CGPoint oldCenterOnImage = [self.layer convertPoint:centerPoint toLayer:self.mapOverlayView.layer]; // Actual non transformed point
// This point is verified to be the corresponding non transformed center point
CGPoint newCenterOnImage = [self calculateNewCenterFor:oldCenterOnImage fromFloor:oldFloorNr toFloor:newFloorNr];
// Change image, sets a new image and change the fram of mapOverlayView
[self changeImageFromFloor:oldFloorNr toFloor:newFloorNr]
// Adjust transformed rotation on map if new map have different rotation
[self adjustRotationFromFloorNr:oldFloorNr toFloorNr:newFloorNr];
CGPoint centerOfMapOverlay = CGPointMake((self.mapOverlayView.frame.size.width / 2), (self.mapOverlayView.frame.size.height / 2));
CGPoint newCenterOnImageTransformed = CGPointApplyAffineTransform(newCenterOnImage, self.mapOverlayView.transform);
CGFloat newCenterX = centerPoint.x + centerOfMapOverlay.x - newCenterOnImageTransformed.x;
CGFloat newCenterY = centerPoint.y + centerOfMapOverlay.y - newCenterOnImageTransformed.y;
// This only works without any rotation
self.mapOverlayView.center = CGPointMake(newCenterX, newCenterY);
}
Any idea where I go wrong? I have been working with this problem some days now and I can not seem to figure it out.
Please let me know if I need to add something or if something is unclear.
Thanks!
Code added after help was given:
CGPoint centerOfMapOverlay = CGPointMake(
(self.mapOverlayView.bounds.size.width / 2,
(self.mapOverlayView.bounds.size.height / 2)
);
centerOfMapOverlay = CGPointApplyAffineTransform(
centerOfMapOverlay,
self.mapOverlayView.transform
);
If you change the transform on it's view then the frame property becomes undefined. You should instead use the center property to change the view's position and bounds.size to change the view's size.

How to smoothly move a UIView with the users finger on iOS

in my app I have a UIImageView representing a chalk. The user can pick that up and drag it over the screen to draw with it.
I implemented that using touchesBegan, touchesMoved and touchesEnded. In touchesMoved I move the center of my UIImageView to the current touch location and draw a line with core graphics from the last to the current location.
This works well but the image view movement isn't very smooth and it also lags behind the touch. I already tried to use a UIPanGestureRecognizer (didn't recognize the gesture) and a UIScrollView in which I placed the chalk (didn't really figure out how to configure it so that it could be moved far enough in all directions).
Can you give me some hints how to improve the quality of my chalk movement?
Thanks!
The following tapGesture method method works smooth for me:
- (void)handlePan:(UIPanGestureRecognizer*)gesture{
View* view = (View*)gesture.view;
if (gesture.state == UIGestureRecognizerStateEnded){
view.dragStartingPoint = CGPointZero;
return;
}
CGPoint point = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan){
view.dragStartingPoint = point;
view.dragStartingFrame = view.frame;
return;
}
if (CGPointEqualToPoint(view.dragStartingPoint, CGPointZero))
return;
CGFloat x = view.dragVerticallyOnly? view.frame.origin.x: view.dragStartingFrame.origin.x + point.x - view.dragStartingPoint.x;
CGFloat y = view.dragHorizontallyOnly? view.frame.origin.y: view.dragStartingFrame.origin.y + point.y - view.dragStartingPoint.y;
if (self.dragVerticallyOnly == NO){
if (x < view.dragMinPoint.x){
x = view.dragMinPoint.x;
}
else if (x > self.dragMaxPoint.x){
x = view.dragMaxPoint.x;
}
}
if (self.dragHorizontallyOnly == NO){
if (y < view.dragMinPoint.y){
y = view.dragMinPoint.y;
}
else if (y > self.dragMinPoint.y){
y = view.dragMinPoint.y;
}
}
CGFloat deltaX = x - view.frame.origin.x;
CGFloat deltaY = y - view.frame.origin.y;
if ((fabs(deltaX) <= 1.0 && fabs(deltaY) <= 1.0))
return; // Ignore very small movements
[UIView animateWithDuration:0.1 animations:^{
view.frame = CGRectMake(x, y, view.frame.size.width, view.frame.size.height);
}];
}
The most important points are:
Make the movement with the animated option; (one tenth of a second seems to do the job well done).
Avoid making movements when not necessary. Making some calculations will not make it slower, but making unnecessary movements might. However you cannot avoid to many movements otherwise it will not follow the user pan gesture.
Depending on how you want your view to behave, there can be more optimisations that you may want to make.
Remark: My method also takes into account boundaries and movement directions limits that you may define in case you need it. For instance, in my case I added a speed limit. If the user goes beyond a certain pan speed I just ignore further pan gestures and move the view (animated) where the user was pointing to, and at that speed. It may or not make sense in same cases. In mine, it did!
I hope I have been of some help!
look at this component : spuserresizableview
http://cocoacontrols.com/platforms/ios/controls/spuserresizableview
The source code is very simple and can help you to understand the view handling.
Try reducing the size of the Chalk image u are using....
Edited: By size i meant the image file size.
It helped me when i faced similar issue.
I rewrote how the movement is calculated and it is a little smoother now. Not perfect, but it's enough.

Cocos2D prevent sprite from going off-screen?

Is it possible to prevent my CCSprite from going off-screen? I already allow it to go offscreen on the left and right so that is fine but I just want to stop it from going off screen on the top and bottom.
So far what I have done is just cause the sprite to just get stuck on either the top or bottom. I don't want this to affect the movement of the sprite, all I want to happen is the CCSprite will just stop when it hits the top or bottom.
Can anyone show me how to do this?
Thanks!
Edit:
CGSize size = [[CCDirector sharedDirector] winSize];
if ((sprite.y <= size.height) && (sprite.y >= 0) ) {
// Set new position
} else {
// sprite is colliding with top/bottom limits, do whatever you like, for example change direction
}
To limit the sprite within a boundary, don't check the current position but check the new position instead. But, rather than using (possibly multiple) if conditions, you can use clamping method:
Technique 1 - using MIN and MAX combo:
CGPoint newPosition = ... (assign new position here using touch location or something)
sprite.position = ccp(newPosition.x, MAX(0, MIN(size.height, newPosition.y)));
Technique 2 - using clampf:
CGPoint newPosition = ... (assign new position here using touch location or something)
sprite.position = ccp(newPosition.x, clampf(newPosition.y, 0, size.height));
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite* sprite = [CCSprite node];
CGSize spriteSize = sprite.boundingBox.size;
if ((sprite.position.y + spriteSize.height/2 < 0 )||(sprite.position.y + spriteSize.height/2 > winSize.height) ) {
//Sprite is out of screen
}
not tested, but since you have the anchorpoint at 0.5, 0.5 as standard this should work for you

rotate UIImageView around an arbitrary point

I have a UIImageView that I rotate around its center:
imageHorizon.layer.anchorPoint = CGPointMake(0.5, 0.5);
imageHorizon.transform = CGAffineTransformRotate(imageHorizon.transform, angleToRotate*(CGFloat)(M_PI/180));
Sometimes I also move this image to the left or right and then rotate again. I would like to keep the rotation center all the time on the same point (which is actually the center of the super view). How can I do that ?
cheers,
self.imgView.layer.anchorPoint = CGPointMake(0.0,1.0);
self.imgView.layer.position = CGPointMake(100,200.0);
CGAffineTransform cgaRotateHr = CGAffineTransformMakeRotation(-(3.141/4));
[self.imgView setTransform:cgaRotateHr];
This is an older question, but the other solutions did not work well for me, so I came up with another solution:
Rotating an image is essentially just a normal rotation with a translation applied, ensuring that the point you want to rotate around is still in the same spot after the rotation. To do this, calculate the position's CGPoint in your image before the rotation, get the position after the rotation, and apply the difference as a translation on the image, "snapping" it into the right position. Here is the code that I've been using:
Keep in mind that the translation should be applied via CGAffineTransform, not moving the .center, because the translation will need to be relative to the rotation, and CGAffineTransformTranslate() takes care of that.
// Note: self is the superview of _imageView
// Get the rotation point
CGPoint rotationPointInSelf = self.center; // or whatever point you want to rotate around
CGPoint rotationPointInImage = [_imageView convertPoint:rotationPointInSelf fromView:self];
// Rotate the image
_imageView.transform = CGAffineTransformRotate(_imageView.transform, angle);
// Get the new location of the rotation point
CGPoint newRotationPointInImage = [_imageView convertPoint:rotationPointInSelf fromView:self];
// Calculate the difference between the point's old position and its new one
CGPoint translation = CGPointMake(rotationPointInImage.x - newRotationPointInImage.x, rotationPointInImage.y - newRotationPointInImage.y);
// Move the image so the point is back in it's old location
_imageView.transform = CGAffineTransformTranslate(_imageView.transform, -translation.x, -translation.y);
You can make the image a subview of another view and then rotate the superview to get that effect. Another approach is to set the anchorPoint property as described in the docs.
I'm using this code to rotate around the point (0,0).
Maybe it help you figure out how to active what you want.
float width = self.view.frame.size.width;
float height = self.view.frame.size.height;
CGRect frame_smallView = CGRectMake(-width, -height, width, height);
UIView *smallView = [[UIView alloc] initWithFrame:frame_smallView];
smallView.backgroundColor = darkGrayColor;
// Select x and y between 0.0-1.0.
// The default is (0.5f,0.5f) that is the center of the layer
// (1.0f,1.0f) is the right bottom corner
smallView.layer.anchorPoint = CGPointMake(1.0f, 1.0f);
// Rotate around this point
smallView.layer.position = CGPointMake(0, 0);
[self.view insertSubview:smallView belowSubview:self.navBar];
[UIView animateWithDuration:1
animations:^{
smallView.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[self.navigationController popViewControllerAnimated:NO];
}];

How to track successive CGAffineTransforms so as to arrive at a specific point on screen?

How do you execute multiple CGAffineTransform operations (in animation blocks) without keeping track of every operation executed?
The translation operation doesn't take x,y coordinates but instead values to shift by. So unless you know where you are currently translated to, say at "location 2," how do you know what values to shift by to get to "location 3?"
For example:
A UIView's frame is at (0, 0) - Position 1. I set the transform to translate to (768, 0) and rotate -90 degrees - Position 2. Some time passes and now I want to move to (768, 1024) and rotate another -90 degrees - Position 3.
How do I know what to translate by to move from Position 2 to Position 3?
In context, I'm trying to achieve an iPad view like the following:
a UIView that takes up the entire screen
a UIToolbar that takes up the top edge and is on top of the UIView
when the iPad rotates, the UIView stays with the device, but the toolbar will rotate so that it is always on the top edge of the screen.
I am using CGAffineTransform translate and rotate to move the toolbar. Works great, except when I rotate the iPad multiple times. The first translate/rotate will work perfect. The following transforms will be off because I don't know the correct values to shift by.
UPDATE:
It looks like if I take the current translation (tx, ty) values in the UIView.transform struct and use the difference between them and the new location, it works. However, if the view has been rotated, this does not work. The tx and ty values can be flipped because of the previous rotation. I'm not sure how to handle that.
UPDATE 2:
Doing some research, I've found that I can get the original, unrotated points from tx, ty by getting the abs value of the points and possibly swapping x and y if the UIView is perpendicular. Now I am stuck figuring out how to correctly apply the next set of transforms in the right order. It seems no matter how I concat them, the UIView ends up in the wrong place. It just seems like this is all too complicated and there must be an easier way.
The answer is, apparently, you don't track the transforms.
So the way to rotate the toolbar around the screen is by not concatenating a rotate and translate transform. Instead, create a rotate transform and set the frame in the animation block. Further, based on the new UIInterfaceOrientation, set the degrees to rotate based on the compass values of 0, -90, -180, -270. Also, set the frame size base on the same locations.
So:
CGPoint portrait = CGPointMake(0, 0);
CGPoint landscapeLeft = CGPointMake(768 - 44, 0);
CGPoint landscapeRight = CGPointMake(0, 0);
CGPoint upsideDown = CGPointMake(0, 1024 - 44);
CGSize portraitSize = CGSizeMake(768, 44);
CGSize landscapeLeftSize = CGSizeMake(44, 1024);
CGSize landscapeRightSize = CGSizeMake(44, 1024);
CGSize upsideDownSize = CGSizeMake(768, 44);
CGFloat rotation;
CGRect newLocation;
switch(orientation) {
case UIDeviceOrientationPortrait:
NSLog(#"Changing to Portrait");
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
case UIDeviceOrientationLandscapeRight:
NSLog(#"Changing to Landscape Right");
newLocation.origin = landscapeRight;
newLocation.size = landscapeRightSize;
rotation = -90.0;
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(#"Changing to Landscape Left");
newLocation.origin = landscapeLeft;
newLocation.size = landscapeLeftSize;
rotation = -270.0;
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(#"Changing to Upside Down");
newLocation.origin = upsideDown;
newLocation.size = upsideDownSize;
rotation = -180.0;
break;
default:
NSLog(#"Unknown orientation: %d", orientation);
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
}
CGRect frame = newLocation;
CGAffineTransform transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotation));
if(lastOrientation) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationBeginsFromCurrentState:YES];
}
toolbar.transform = transform;
toolbar.frame = frame;
// Commit the changes
if(lastOrientation) {
[UIView commitAnimations];
}
lastOrientation = orientation;
This works beautifully. However, an unexpected problem is that UI elements that iOS shows on your behalf are not oriented correctly. I.e., modal windows and popovers all keep the same orientation as the underlying UIView. That problem renders this whole thing moot.

Resources