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

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.

Related

how do I make the base of each UIView a tangent to a circle so they radiate from the centre?

I am trying to find angles of rotation for a series of light and dark rectangular UIViews placed at regular points on a circle perimeter. Each point on the circle is calculated as an angle of displacement from the centre and I have tried using the same angle to rotate each UIView so it radiates from the centre. But I didn't expect it to look like this.
I expected the angle of displacement from the centre to be the same as the angle of rotation for each new UIView. Is this assumption correct ? and if so, how do I make the base of each UIView a tangent to a circle so they radiate from the centre ?
UPDATE
In case someone finds it useful here is an update of my original code. The problem as explained by rmaddy has been rectified.
I’ve included two versions of the transform statement and their resulting rotated UIViews. Result on the left uses radians + arcStart + M_PI / 2.0, result on right uses radians + arcStart.
Here is the method.
- (void)sprocket
{
CGRect canvas = [UIScreen mainScreen].bounds;
CGPoint circleCentre = CGPointMake((canvas.size.width)/2, (canvas.size.height)/2);
CGFloat width = 26.0f;
CGFloat height = 50.0f;
CGPoint miniViewCentre;
CGFloat circleRadius = 90;
int miniViewCount = 16;
for (int i = 0; i < miniViewCount; i++)
{
// to place the next view calculate angular displacement along an arc
CGFloat circumference = 2 * M_PI;
CGFloat radians = circumference * i / miniViewCount;
CGFloat arcStart = M_PI + 1.25f; // start circle from this point in radians;
miniViewCentre.x = circleCentre.x + circleRadius * cos(radians + arcStart);
miniViewCentre.y = circleCentre.y + circleRadius * sin(radians + arcStart);
CGPoint placeMiniView = CGPointMake(miniViewCentre.x, miniViewCentre.y);
CGRect swivellingFrame = CGRectMake(placeMiniView.x, placeMiniView.y, width, height);
UIView *miniView = [[UIView alloc] initWithFrame:swivellingFrame];
if ((i % 2) == 0)
{
miniView.backgroundColor = [UIColor darkGrayColor];
}
else
{
miniView.backgroundColor = [UIColor grayColor];
}
miniView.layer.borderWidth = 1;
miniView.layer.borderColor = [UIColor blackColor].CGColor;
miniView.layer.cornerRadius = 3;
miniView.clipsToBounds = YES;
miniView.layer.masksToBounds = YES;
miniView.alpha = 1.0;
// using the same angle rotate the view around its centre
miniView.transform = CGAffineTransformRotate(miniView.transform, radians + arcStart + M_PI / 2.0);
[page1 addSubview:miniView];
}
}
The problem is your calculation of the center of each miniView is based on radians plus arcStart but the transform of each miniView is only based on radians.
Also note that angle 0 is at the 3 o'clock position of the circle. You actually want a 90° (or π/2 radians) rotation of miniView so the rectangle "sticks out" from the circle.
You need two small changes to make your code work:
Change the loop to:
for (int i = 0; i < miniViewCount; i++)
And change the transform:
miniView.transform = CGAffineTransformRotate(miniView.transform, radians + arcStart + M_PI / 2.0);

iOS: derive angle of tap point given a circle

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 .

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.

Placing points on circle with same distance always beginning at same point

I already know how to place the points with same distance on top of a circle:
double slice = 2 * M_PI / [icons count];
for (int i = 0; i < [icons count]; i++)
{
double angle = slice * i;
int newX = (int)(cen.x + rad * cos(angle));
int newY = (int)(cen.y + rad * sin(angle));
CGPoint point = CGPointMake(newX, newY);
}
Depending on the number of elements in my array the position of the points is always different (of course) but how can I manage to put the first point always at the same spot on the circle e.g. at the top most point of the circle?
Add a constant value to your angle. The points should start out on the right of the origin (in standard cartesian coordinates where 0,0 is in the center and X and Y increase to the right and up.)
To shift the first point to the top, add pi/2 to your angle.
It looks like you're using iOS coordinates, where 0,0 is at the top left of the sceen and Y increases DOWN, which flips normal cartesian coordinates on the X axis. Thus you'd need to subtract pi/2 from the angle:
double slice = 2 * M_PI / [icons count];
for (int i = 0; i < [icons count]; i++)
{
double angle = slice * i - M_PI_2;
int newX = (int)(cen.x + rad * cos(angle));
int newY = (int)(cen.y + rad * sin(angle));
CGPoint point = CGPointMake(newX, newY);
}

UIPanGestureRecognizer - Translations and rotations not happening evenly with negatives vs positives

Towards the top of the file I have this:
#define DEGREES_TO_RADIANS(x) (M_PI * x / 180.0)
In viewdidload I have this:
imageView.layer.anchorPoint = CGPointMake(0.5,0.5);
Then finally in the gesture recognizer I have this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:self.view];
xPos += translation.x;
if(xPos > 150) xPos = 150;
if(xPos < -150) xPos = -150;
float rotate = 0;
if(xPos >= 50) {
rotate = (xPos - 50) * 0.025;
} else if(xPos <= -50) {
rotate = (xPos + 50) * 0.025;
}
imageView.transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotate)), (xPos * 0.35), 0);
}
This appears to be applying the exact same effects to negatives (left) and positives (right).
With the UIImageView that is being rotated being placed in the center of the screen horizontally, and its anchor set to the center I would expect the maximum in both directions to be cut off equally by the edge of the screen. Instead the image goes much further to the right.
Left effect (negative):
Right effect (positive):
It looks like autolayout changes the anchor point as the image view is rotated and the frame changes.

Resources