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);
}
Related
I designed an app where the user taps the ball as many times as they can within 30 seconds called iTapping. In the game, the user is able to tap anywhere on the screen for the ball to be tapped. I thought of editing the app so that there would be 'dead spots' where the user would not be able to tap the ball. For example, if the ball is in the upper right-hand corner (lets say in an area of about 100 sq. pts.), and the user taps the ball nothing happens. How would I code this? Please let me know if this is not clear enough.
Here is the .m file:
CGPoint Destination;
CGFloat xamt, yamt;
CGFloat speed21 = 40;
CGFloat xMin21 = 24;
CGFloat xMax21 = 297;
CGFloat yMin21 = 74;
CGFloat yMax21 = 454;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if ([self Intersecting:location :Ball]) {
number21++;
int xRange = xMax21 - xMin21;
int yRange = yMax21 - yMin21;
int xRand = (arc4random() % xRange) + xMin21;
int yRand = (arc4random() % yRange) + yMin21;
Destination = CGPointMake(xRand, yRand);
xamt = ((Destination.x - Ball.center.x) / speed21);
yamt = ((Destination.y - Ball.center.y) / speed21);
if (number21 == 65) {
[timer invalidate];
}
}
}
-(BOOL)Intersecting:(CGPoint)loctouch:(UIImageView *)enemyimg {
CGFloat x1 = loctouch.x;
CGFloat y1 = loctouch.y;
CGFloat x2 = enemyimg.frame.origin.x;
CGFloat y2 = enemyimg.frame.origin.y;
CGFloat w2 = enemyimg.frame.size.width;
CGFloat h2 = enemyimg.frame.size.height;
if ((x1>x2)&&(x1<x2+w2)&&(y1>y2)&&(y1<y2+h2))
return YES;
else
return NO;
}
I suggest to put the touch management back to the ball itself (see this answer). So whenever a touch is received, you can be sure it's clicking on the ball. All you need to do is to check whether the ball is currently in the dead zone, thus ignore the touch.
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...
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)...
I have a PNG of a circle with a transparent background added as a subview. I'm using this type of method to rotate it:
CGPoint location = [touch locationInView:self.view];
if(CGRectContainsPoint(wheelfrom.frame, location))
{
}
the problem is that the transparent area's of the image are registering as part of the UIView. Is there a way to ignore those area's when touched? Is there a better way for me to set up the UIView to recognize the transparency?
thanks!
you can check the rbga pixel colour of the image and see if a (=alpha value) is == 0 (or <= aLowValue)... as suggested by Igor Pchelko...
but in your case it may be easier...
you are using a 2D circle, so just check how the finger click is far from the circle center and see if it's out of its radius... just a Pitagora's theorem application...
EDIT:
ok, so, if you create a new class for your button subclassing UIButton:
in YourButton.h:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface YourButton : UIButton {
}
#end
in YourButton.m just add this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self];
NSLog(#"Touch x : %f y : %f", touchPoint.x, touchPoint.y);
float circleRadius = self.frame.size.height / 2; // considering a circle inscricted in a quadRect (width = height)
float deltaTouchOnCenterX = touchPoint.x - circleRadius;
float deltaTouchOnCenterY = touchPoint.y - circleRadius;
float touchDistanceFromCenter = sqrt((deltaTouchOnCenterX * deltaTouchOnCenterX) + (deltaTouchOnCenterY * deltaTouchOnCenterY) );
// or: float touchDistanceFromCenter = hypot(deltaTouchOnCenterX,deltaTouchOnCenterY);
NSLog(#"sqrt_val: %f", touchDistanceFromCenter);
NSLog(#"Touch on center x : %f y : %f", deltaTouchOnCenterX, deltaTouchOnCenterY);
if (touchDistanceFromCenter <= circleRadius) {
// your code here
NSLog(#"___ OK: You are inside the circle!!");
}
}
Swift solution:
class RadiusTouchButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let radius = frame.size * 0.5
let delta = point - CGPoint(x: radius.height, y: radius.width)
return hypot(delta.x, delta.y) <= radius.height
}
}
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] ;
}