iOS: derive angle of tap point given a circle - ios

I have got a UIImageView displaying a circle divided in six equal triangles corresponding to:
area1 between 0-60 degrees
area2 between>60-120 degrees
area3 between>120-180 degrees
area4 between>180-240 degrees
area5 between>240-300 degrees
area6 between>300-360 degrees
The circle is similar to the following (pardon me for the bad drawing):
I would like to derive from the touch point in which area the tap is. For example if the user taps at the top right corner of the circle then the area should be area 2: ">60-120".
The input data I got is:
width and height of the frame containing the circle (e.g. 200 pixel wide, 200 pixels height)
tap point coordinates
Any suggestion on how to infer in which area the tap point falls given the input data described above?

I just noticed some errors in this solution I'd mentioned in the comments, but it's generally what you need...
I recommend getting the angle between your tap point and your circle's frame's center then implementing a getArea function to get the particular area tapped within your circle, ex:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint tapPoint = [touch locationInView:self.view];
CGFloat angle = [self angleToPoint:tapPoint];
int area = [self sectionForAngle:angle];
}
- (float)angleToPoint:(CGPoint)tapPoint
{
int x = self.circle.center.x;
int y = self.circle.center.y;
float dx = tapPoint.x - x;
float dy = tapPoint.y - y;
CGFloat radians = atan2(dy,dx); // in radians
CGFloat degrees = radians * 180 / M_PI; // in degrees
if (degrees < 0) return fabsf(degrees);
else return 360 - degrees;
}
- (int)sectionForAngle:(float)angle
{
if (angle >= 0 && angle < 60) {
return 1;
} else if (angle >= 60 && angle < 120) {
return 2;
} else if (angle >= 120 && angle < 180) {
return 3;
} else if (angle >= 180 && angle < 240) {
return 4;
} else if (angle >= 240 && angle < 300) {
return 5;
} else {
return 6;
}
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let tapPoint = touch?.location(in: self)
let distance = distanceWithCenter(center: circleCenter,SCCenter: tapPoint!)
if distance <= radius {
let angle = angleToPoint(tapPoint: tapPoint!)
print(angle)
}
func angleToPoint(tapPoint:CGPoint) -> CGFloat{
let dx = circleCenter.x - tapPoint.x
let dy = circleCenter.y - tapPoint.y
let radians = atan2(dy, dx) + π // Angel in radian
let degree = radians * (180 / π) // Angel in degree
print("angle is off = \(degree)")
return degree
}
You got the angle now check in which section it belong.You can figure it out by comparision.If you face any problem please let me know .

Related

UITapGesture to drag view but limiting the bounds of which it can be dragged

I currently have 2 circles. One big circle and one little circle. The little circle has a tap gesture recognizer that allows it to be dragged by the user. I would like the little circle's center to go no further than the big circle's radius. I have 4 auto layout constraints on the inner circle. 1 for fixed width, 1 for fixed height, 1 for distance from center for x, and 1 for distance from center for y. Here is how I am going about this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.view];
CGFloat x = recognizer.view.center.x + translation.x;
CGFloat y = recognizer.view.center.y + translation.y;
CGPoint desiredPoint = CGPointMake(x, y);
//check if point the user is trying to get to is outside the radius of the outer circle
//if it is, set the center of the inner circle to the right position at the distance of the radius and with the same angle
if ([self distanceBetweenStartPoint:self.outerCircleView.center endPoint:desiredPoint] > self.outerCircleRadius) {
CGFloat angle = [self angleBetweenStartPoint:self.outerCircleView.center endPoint:actualPosition];
desiredPoint = [self findPointFromRadius:self.outerCircleRadius startPoint:self.outerCircleView.center angle:angle];
}
//adjust the constraints to move the inner circle
self.innerCircleCenterXConstraint.constant += actualPosition.x - recognizer.view.center.x;
self.innerCircleCenterYConstraint.constant += actualPosition.y - recognizer.view.center.y;
[recognizer setTranslation:CGPointMake(0.0, 0.0) inView:self.view];
}
}
- (CGFloat)distanceBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGFloat xDif = endPoint.x - startPoint.x;
CGFloat yDif = endPoint.y - startPoint.y;
//pythagorean theorem
return sqrt((xDif * xDif) + (yDif * yDif));
}
- (CGPoint)findPointFromRadius:(CGFloat)radius startPoint:(CGPoint)startPoint angle:(CGFloat)angle {
CGFloat x = radius * cos(angle) + startPoint.x;
CGFloat y = radius * sin(angle) + startPoint.y;
return CGPointMake(x, y);
}
- (CGFloat)angleBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
return atan2f(originPoint.y, originPoint.x);
}
This works almost perfectly. The problem is I try to find the percentage that the user moved towards the outside of the circle. So I use the distanceBetweenStartPoint(center of outer circle) endPoint(center of inner circle) method and divide that by the radius of the outer circle. This should give me a value of 1 when the circle has been dragged as far to one side as it can go. Unfortunately I am getting values like 0.9994324 or 1.000923. What could be causing this? Thanks for any insight!

draw a dial like line pointing at user's touch point

Assuming I want to draw a line which resemble a clock dial (blue line), starting from the center of the screen (center) and ending at the user's touch position (A,B or C)
it does not matter how far the finger is, the dial will always have the same length size.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"Center point = %f %f",self.view.center.x,self.view.center.y);
NSLog(#"finter at point = %f %f",touchLocation.x,touchLocation.y);
// line re drawing itself ...
NSLog(#"end point = %f %f",?,?);
}
You need to know the length of your line. It's unrelated to the touch point correct?
First find the coordinates of the touch relative to the center point
x = Touch.x - center.x
y = Touch.y - center.y
Now we need to get the angle
angle = arctan(y / x)
If the x is negative, adjust by 180 degrees (pi) - This restores what is lost in the division.
Now multiply sin(angle) and cos(angle) by your desired length to get the new point
newX = cos(angle) * length
newY = sin(angle) * length
Here is some Swift code that gets you mostly there. Try it in a playground to verify different touch values and lengths.
let lineLength = 13.0
// Touch points
let x = -5.0
let y = -5.0
// Calculate angle
var angle = atan(y / x)
if x < 0 {
angle += 3.14159;
}
// Get new X and Y
var newX = cos(angle) * lineLength
var newY = sin(angle) * lineLength

Rotating UIView (Again)

I'm encountering some problems with rotating a uiview again. This time, im trying to rotate a uiview 1/12 the speed of another uiview I'm rotating at normal speed. However, when I try to accomplish this task, the uiview I'm trying to move slower moves like this:
1st Update
https://www.youtube.com/watch?v=wj3nRJo5CMM&feature=youtu.be
2nd Update
https://www.youtube.com/watch?v=YLRkUzXSDtQ&feature=youtu.be
Here's my code:
- (void)rotateHand:(UIPanGestureRecognizer *)panGesture {
if ([(UIPanGestureRecognizer*)panGesture state] == UIGestureRecognizerStateBegan) {
CGPoint touchPoint = [panGesture locationInView:[self view]];
float dx = touchPoint.x - minHandContainer.center.x;
float dy = touchPoint.y - minHandContainer.center.y;
arcTanMin = atan2(dy,dx);
arcTanHour = atan2(hourHand.center.x - minHandContainer.center.x, hourHand.center.y - minHandContainer.center.y);
if (arcTanMin < 0) {
arcTanMin = 2 * M_PI + arcTanMin;
}
if (arcTanHour < 0) {
arcTanHour = 2 * M_PI + arcTanMin;
}
NSLog(#"arcTanMin %f", arcTanMin);
startTransformMin = minHandContainer.transform;
startTransformHour = hourHandContainer.transform;
}
if ([(UIPanGestureRecognizer*)panGesture state] == UIGestureRecognizerStateChanged) {
CGPoint pt = [panGesture locationInView:[self view]];
float dx = pt.x - minHandContainer.center.x;
float dy = pt.y - minHandContainer.center.y;
float ang = atan2(dy,dx);
if (ang < 0) {
ang = 2 * M_PI + ang;
}
float angleDifferenceM = arcTanMin - ang;
float angleDifferenceH = arcTanHour + angleDifferenceM * (1.0/12.0);
NSLog(#"angleDiffM %f", angleDifferenceM);
NSLog(#"angleDiffH %f", angleDifferenceH);
minHandContainer.transform = CGAffineTransformRotate(startTransformMin, -angleDifferenceM);
hourHandContainer.transform = CGAffineTransformRotate(startTransformHour, -angleDifferenceH);
}
}
It appears that you're using arcTanMin as the starting reference angle for both the minute hand and hour hand. So when you make the jump across the x-axis, both angleDifferenceM and angleDifferenceH are making the jump (which is why at the moment of the jump the angle of the hour hand to the y-axis is the same as the angle of the minute hand to the x-axis), but angleDifferenceH doesn't need to make the jump. Change this:
float angleDifferenceH = angleDifferenceM * (1.0/12.0);
to
float angleDifferenceH = arcTanHour + angleDifferenceM * (1.0/12.0);
with an appropriate starting value for arcTanHour.

How to I rotate a UIView a fix +/- angle?

I want to use a UISlider to rotate +/- angles from 0 to 180 degrees (pi).
My problem is that the rectangle rotates either before 0 or beyond 180.
What I need is to essentially map to relative UISlider button position with the angle.
Here's the code, with the UIView.layer's anchor at one end (as in a dial):
- (IBAction)sliderAction:(UISlider *)sender {
static float datum = 0;
if (sender.value > 0 && sender.value < 10) {
float myValue = 0;
if (datum < sender.value) {
myValue = +0.1;
} else {
myValue = -0.1;
}
_gageStickView.transform = CGAffineTransformRotate(_gageStickView.transform, myValue);
datum = sender.value;
}
This rotates it, but I don't have adequate control over it.
Question: How can I map the the UISlider's position with the angle of the UIView?
That is, left-slider at 0 deg., mid-sider at 90deg and right-slider at 180 degs; and in-between.
Like #Larme says, you need to convert degrees to radians.
- (IBAction)sliderValueChanged:(UISlider *)sender {
CGFloat degrees = (sender.value / sender.maximumValue) * 180;
CGFloat radians = degrees * M_PI / 180;
self.label.transform = CGAffineTransformMakeRotation(radians);
}
This code isn't hardwired to a value of 10, but just get's the 'percentage', based on the maximum value of the slider.

UIView touch location coordinates

What are the coordinates used in UIViews and their corresponding superviews? I have this code which i would like to detect a 'corridor' where the user can touch... similar to this image:alt text http://img17.imageshack.us/img17/4416/bildschirmfoto20100721u.png
This is the code i have:
CGPoint touch = [recognizer locationInView:[shuttle superview]];
CGPoint centre = shuttle.center;
int outerRadius = shuttle.bounds.size.width/2;
int innerRadius = (shuttle.bounds.size.width/2) - 30;
if ((touch.x < outerRadius && touch.y <outerRadius)){
NSLog(#"in outer");
if(touch.x > innerRadius && touch.y > innerRadius) {
NSLog(#"in corridor");
}
}
The radii are approximately 500 and 600, and the touch x and y are 100 and 200...
Thus, the NSLog "in corridor" never gets called.
Thanks
Your condition is wrong. The corridor according to it is a square, with its center at (0, 0) instead of shuttle.center. Try
CGFloat dx = touch.x - centre.x;
CGFloat dy = touch.y - centre.y;
CGFloat r2 = dx*dx + dy*dy;
if (r2 < outerRadius*outerRadius) {
NSLog(#"in outer");
if (r2 > innerRadius*innerRadius)
NSLog(#"in corridor")
}
instead.
Even if the corridor is indeed expected to be a square, you should check with fabs(dx), fabs(dy) not touch.x, touch.y.

Resources