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

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

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

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!

Adding numbers to a clock face?

I saw this clock example on objc.io and I wanted to add the numbers to the clock face.
http://www.objc.io/issue-12/animating-custom-layer-properties.html
So, in the -(id)init{} method I added this code using a formula that I found on the Internet:
float Cx = self.bounds.size.width / 2;
float Cy = self.bounds.size.height / 2;
float Rx = 100;
float Ry = 100;
for (int i=1; i<=12; i++) {
float theta = i / 12.0 * 2.0 * M_PI;
float X = Cx + Rx * cos(theta);
float Y = Cy + Ry * sin(theta);
CATextLayer *aTextLayer = [[CATextLayer alloc] init];
[aTextLayer setFont:#"Helvetica-Bold"];
[aTextLayer setFontSize:20];
[aTextLayer setFrame:CGRectMake(X, Y, 50, 20)];
[aTextLayer setString:[NSString stringWithFormat:#"%d", i]];
[aTextLayer setAlignmentMode:kCAAlignmentCenter];
[aTextLayer setForegroundColor:[[UIColor greenColor] CGColor]];
[self addSublayer:aTextLayer];
}
For some reason, my 12 o'clock has shifted to 3 o'clock.
Can anyone spot what I'm doing wrong?
The trig functions wind from the positive x-axis toward the positive y-axis. In a CALayer, positive y is down, so the start of your loop is 3 o'clock, and the angle proceeds clockwise, which is how you want it for a clock, and the opposite of how things work on the standard (+y==UP) cartesian plane.
The next thing to notice is that it's simpler to loop twelve positions 0..11 and compute the hours and angles from the position.
Finally, the frames of your labels are set to an arbitrary size #{50,20} and the text is centered, this is probably the cause of the x offset. You want the computed x,y to be the labels' centers, so you'll need to fudge the origins a little...
for (int position=0; position <12; position ++) {
int hour = (position+3) % 12;
float theta = position / 6.0 * M_PI;
float X = Cx + Rx * cos(theta);
float Y = Cy + Ry * sin(theta);
// X,Y calculated as you have done here should be the center of the layer
// the quick fix is to subtract half the width and height to get the origin
CGRect frame = CGRectIntegral(CGRectMake(X-25, Y-10, 50, 20));
CATextLayer *aTextLayer = [[CATextLayer alloc] init];
[aTextLayer setFont:#"Helvetica-Bold"];
[aTextLayer setFontSize:20];
[aTextLayer setFrame:frame];
// note that we use "hour" here...
[aTextLayer setString:[NSString stringWithFormat:#"%d", hour]];
[aTextLayer setAlignmentMode:kCAAlignmentCenter];
[aTextLayer setForegroundColor:[[UIColor greenColor] CGColor]];
[self addSublayer:aTextLayer];
}
You might find it helpful to use a radius for the positioning of these labels that's distinct from the radius used to draw the circle. To position the numbers inside, use a smaller radius, or use a larger one to draw them outside the filled dial.
From your formula it looks like if you swapped the Cos and Sin then negated the Cos term you might get what you're looking for.
Edit: meant to include this too. I noticed that they did this too at the link you posted one of there lines of code where they draw the clock hands is this:
CGContextAddLineToPoint(ctx, center.x + sin(angle) * 80, center.y - cos(angle) * 80);
it looks like they also swapped the cos and sin and negated the cos term

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.

iOS using accelerometer to move an object within a circle

I am trying use the accelerometer to move an image within a circle. I am having issue that when the image hits the edge of the circle, it just moves the other side of the circle. My code is below:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
//NSLog(#"x : %g", acceleration.x);
//NSLog(#"y : %g", acceleration.y);
//NSLog(#"z : %g", acceleration.z);
delta.x = acceleration.x * 10;
delta.y = acceleration.y * 10;
joypadCap.center = CGPointMake(joypadCap.center.x + delta.x, joypadCap.center.y - delta.y);
distance = sqrtf(((joypadCap.center.x - 160) * (joypadCap.center.x - 160)) +
((joypadCap.center.y -206) * (joypadCap.center.y - 206)));
//NSLog(#"Distance : %f", distance);
touchAngle = atan2(joypadCap.center.y, joypadCap.center.x);
NSLog(#"Angle : %f", touchAngle);
if (distance > 50) {
joypadCap.center = CGPointMake(160 - cosf(touchAngle) * 50, 206 - sinf(touchAngle) * 50);
}
I was having the same issue when attempting to implement a circular spirit level using CMDeviceMotion. I found it was an issue with the coordinates passed to atan2(y,x). This function requires cartesian coordinates, with (0,0) in the centre of the view. However, the screen coordinates have (0,0) in the top left corner. I created methods to convert a point between the two coordinate systems, and now it's working well.
I put up a sample project here on github, but here's the most important part:
float distance = sqrtf(((point.x - halfOfWidth) * (point.x - halfOfWidth)) +
((point.y - halfOfWidth) * (point.y - halfOfWidth)));
if (distance > maxDistance)
{
// Convert point from screen coordinate system to cartesian coordinate system,
// with (0,0) located in the centre of the view
CGPoint pointInCartesianCoordSystem = [self convertScreenPointToCartesianCoordSystem:point
inFrame:self.view.frame];
// Calculate angle of point in radians from centre of the view
CGFloat angle = atan2(pointInCartesianCoordSystem.y, pointInCartesianCoordSystem.x);
// Get new point on the edge of the circle
point = CGPointMake(cos(angle) * maxDistance, sinf(angle) * maxDistance);
// Convert back to screen coordinate system
point = [self convertCartesianPointToScreenCoordSystem:point inFrame:self.view.frame];
}
And:
- (CGPoint)convertScreenPointToCartesianCoordSystem:(CGPoint)point
inFrame:(CGRect)frame
{
float x = point.x - (frame.size.width / 2.0f);
float y = (point.y - (frame.size.height / 2.0f)) * -1.0f;
return CGPointMake(x, y);
}
- (CGPoint)convertCartesianPointToScreenCoordSystem:(CGPoint)point
inFrame:(CGRect)frame
{
float x = point.x + (frame.size.width / 2.0f);
float y = (point.y * -1.0f) + (frame.size.height / 2.0f);
return CGPointMake(x, y);
}

Resources