Core Animation rotation around point - ios

I would like to make an IBOutlet rotate around a specific point in the parent view,
currently i only know how to rotate it around an anchor point, however, i want to use a point outside the object's layer.
The rotation angle is calculated relative to the device heading from the point.
- (void)viewDidLoad{
[super viewDidLoad];
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.headingFilter = 1;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
[locationManager startUpdatingLocation];
compassImage.layer.anchorPoint=CGPointZero;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
// Convert Degree to Radian and move the needle
float oldRad = -manager.heading.trueHeading * M_PI / 180.0f;
float newRad = -newHeading.trueHeading * M_PI / 180.0f;
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
theAnimation.toValue=[NSNumber numberWithFloat:newRad];
theAnimation.duration = 0.5f;
[compassImage.layer addAnimation:theAnimation forKey:#"animateMyRotation"];
compassImage.transform = CGAffineTransformMakeRotation(newRad);
NSLog(#"%f (%f) => %f (%f)", manager.heading.trueHeading, oldRad, newHeading.trueHeading, newRad);
}
How can i rotate the UIImageView around (x,y) by alpha?

For rotating around a specific point (internal or external) you can change the anchor point of the layer and then apply a regular rotation transform animation similar to what I wrote about in this blog post.
You just need to be aware that the anchor point also affects where the layer appears on the screen. When you change the anchor point you will also have to change the position to make the layer appear in the same place on the screen.
Assuming that the layer is already placed in it's start position and that the point to rotate around is known, the anchor point and the position can be calculated like this (note that the anchor point is in the unit coordinate space of the layer's bounds (meaning that x and y range from 0 to 1 within the bounds)):
CGPoint rotationPoint = // The point we are rotating around
CGFloat minX = CGRectGetMinX(view.frame);
CGFloat minY = CGRectGetMinY(view.frame);
CGFloat width = CGRectGetWidth(view.frame);
CGFloat height = CGRectGetHeight(view.frame);
CGPoint anchorPoint = CGPointMake((rotationPoint.x-minX)/width,
(rotationPoint.y-minY)/height);
view.layer.anchorPoint = anchorPoint;
view.layer.position = rotationPoint;
Then you simply apply a rotation animation to it, for example:
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotate.toValue = #(-M_PI_2); // The angle we are rotating to
rotate.duration = 1.0;
[view.layer addAnimation:rotate forKey:#"myRotationAnimation"];
Just note that you have changed the anchor point so the position is no longer in the center of the frame and if you apply other transforms (such as a scale) they will also be relative to the anchor point.

Translated David's answer to swift 3:
let rotationPoint = CGPoint(x: layer.frame.width / 2.0, y: layer.frame.height / 2.0) // The point we are rotating around
print(rotationPoint.debugDescription)
let width = layer.frame.width
let height = layer.frame.height
let minX = layer.frame.minX
let minY = layer.frame.minY
let anchorPoint = CGPoint(x: (rotationPoint.x-minX)/width,
y: (rotationPoint.y-minY)/height)
layer.anchorPoint = anchorPoint;
layer.position = rotationPoint;

Related

How to rotate image view while not changing center X position

I want to rotate my image view using following:
CGFloat degrees = -20.0f; //the value in degrees
CGFloat radians = degrees * M_PI/180;
_arrowImgView.transform = CGAffineTransformMakeRotation(radians);
It does rotate, but unfortunatelly it does rotate whole image moving it up. Please take a look at screenshots:
On second screenshot you can see that image is moved up and changed X coordinate. How to simply bend it like clock arrow?
UPDATED:
I changed code to:
self.arrowImgView.layer.anchorPoint = CGPointMake(0.0f, 0.0f);
CGFloat degrees = -20.0f; //the value in degrees
CGFloat radians = degrees * M_PI/180;
self.arrowImgView.transform = CGAffineTransformMakeRotation(radians);
Also i want to notice that i did set constraints like that :
[_arrowImgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.equalTo(self);
make.centerX.equalTo(self.mas_centerX);
}];
Try this code
imageView.layer.anchorPoint = CGPointMake(0.0f, 0.0f);
imageView.transform = CGAffineTransformMakeRotation(angle);
Reference: https://developer.apple.com/reference/quartzcore/calayer/1410817-anchorpoint
Edit
I tried this below code in swift it works here
self.viewRotate.layer.anchorPoint = CGPoint(x: 0, y: 0)
let degree : CGFloat = -20.0
let rad : CGFloat = degree*CGFloat(M_PI/180)
viewRotate.transform = CGAffineTransform(rotationAngle: rad)
Output
Before rotation
After rotation

How To Get A Perfect Position Of Rotating View?

I am have one view and I am rotating that view using CABasicAnimation.
Now my problem is that how I get a perfect position of that view while rotating. I have tried many type of codes but i can't got a perfect position during rotation of that view.
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [CircleView.layer.presentationLayer valueForKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = currentAngle;
rotationAnimation.toValue = #(50*M_PI);
rotationAnimation.duration = 50.0f; // this might be too fast
rotationAnimation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[CircleView.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
I am using this code for rotating my view.
I have also attached a one photo of my view.
Thank you In Advance Please Help If You Know.
To get view's parameters during the animation you should use view.layer.presentationLayer
ADDED:
In order to get the coordinate of top left corner of the view, use the following code:
- (CGPoint)currentTopLeftPointOfTheView:(UIView *)view
{
CGRect rect = view.bounds;
rect.origin.x = view.center.x - 0.5 * CGRectGetWidth(rect);
rect.origin.y = view.center.y - 0.5 * CGRectGetHeight(rect);
CGPoint originalTopLeftCorner = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint rectCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGFloat radius = sqrt(pow(rectCenter.x - originalTopLeftCorner.x, 2.0) + pow(rectCenter.y - originalTopLeftCorner.y, 2.0));
CGFloat originalAngle = M_PI - acos((rectCenter.x - originalTopLeftCorner.x) / radius);
CATransform3D currentTransform = ((CALayer *)view.layer.presentationLayer).transform;
CGFloat rotation = atan2(currentTransform.m12, currentTransform.m11);
CGFloat resultAngle = originalAngle - rotation;
CGPoint currentTopLeftCorner = CGPointMake(round(rectCenter.x + cos(resultAngle) * radius), round(rectCenter.y - sin(resultAngle) * radius));
return currentTopLeftCorner;
}
Resulting CGPoint will be the coordinate of the top left corner of your (rotated) view relative to its superview.
Set the position of the layer that you what
[layer setPosition:endpoint];
Or you may also refer this CABasicAnimation rotate returns to original position

SKSpriteNode ScaleUpFrom?

Problem
Scaling up a 2D image, scales up from the image centre point, however. I need it to scale up from a specific co-ordinate?
[backgroundImage setScale:rifleZoom];
My current technique to scaling up the image.
I need to scale up from centre screen as opposed to centre image
Now my way to place a rifle shot on the screen # centre is this way:
CGPoint positionNow = CGPointMake(backgroundImage.position.x, backgroundImage.position.y);
CGPoint positionPrev = CGPointMake(0.5, 0.5);
float xdiff = positionNow.x - positionPrev.x;
float ydiff = positionNow.y - positionPrev.y;
CGPoint newPositionOne = CGPointMake(0.5- xdiff, 0.5 - ydiff);
newPositionOne = CGPointMake(newPositionOne.x/rifleZoom, newPositionOne.y/rifleZoom);
Now this works perfectly no matter how much the image is scaled, however. I cannot seem to implement it into scaling the image up from the centre of the screen opposed to centre of the image.
What I've Tried
I've tried to change the position of the image before scaling it up. To the same same point make as newPositionOne However, this does not work or not being done right.
EDIT
This scales and brings centre to screen centrepoint, or messes up completely. It's a little too off the cuff to make a decision exactly what it's doing.
CGPoint positionNow = CGPointMake(backgroundImage.position.x, backgroundImage.position.y);
CGPoint positionPrev = CGPointMake(0.5, 0.5);
float xdiff = positionNow.x - positionPrev.x;
float ydiff = positionNow.y - positionPrev.y;
CGPoint newPositionOne = CGPointMake(0.5- xdiff, 0.5 - ydiff);
newPositionOne = CGPointMake(newPositionOne.x/rifleZoom, newPositionOne.y/rifleZoom);
double xPosition = newPositionOne.x / backgroundImage.size.width;
double yPosition = newPositionOne.y / backgroundImage.size.height;
CGPoint prevAnchorPoint = backgroundImage.anchorPoint;
backgroundImage.anchorPoint = CGPointMake(xPosition,yPosition);
double positionX = backgroundImage.position.x + (backgroundImage.anchorPoint.x - prevAnchorPoint.x) * backgroundImage.size.width;
double positionY = backgroundImage.position.y + (backgroundImage.anchorPoint.y - prevAnchorPoint.y) * backgroundImage.size.height;
backgroundImage.position = CGPointMake(positionX,positionY);
[backgroundImage runAction:[SKAction repeatAction:[SKAction scaleTo:rifleZoom duration:1.0] count:1]];
You can use the anchor point property of the background node to change the point from which image scales. By default the anchorPoint is at (0.5,0.5). This indicates the center of the node. If you make the anchorPoint (0,0), then its moved to the bottom left corner.
anchorPoint : Defines the point in the sprite that corresponds to the
node’s position. You specify the value for this property in the unit
coordinate space. The default value is (0.5,0.5), which means that the
sprite is centered on its position.
When you shift the anchorPoint, you have to adjust the background position again to counteract the movement due to changing the anchorPoint.
So you can use,
CGPoint xPosition = convertedPoint.x / background.size.width
CGPoint yPosition = convertedPoint.y / background.size.height
CGPoint prevAnchorPoint = background.anchorPoint
background.anchorPoint = CGPointMake(xPosition,yPosition)
CGFloat positionX = background.position.x + (background.anchorPoint.x - prevAnchorPoint.x) * background.size.width
CGFloat positionY = background.position.y + (background.anchorPoint.y - prevAnchorPoint.y) * background.size.height
background.position = CGPointMake(positionX,positionY)

Label curve segments

I want to label every part of the following curve like in the figure. The label origin should start from the middle of the curve segment see the graphic. I think that something is not quite right in my logic. Could you assist where is the error in my code? I think that the code and the figure explain itself.
Thanks for your time and help!
CGFloat radius = self.height / 3.2f; // radius of the curve
CGPoint center = CGPointMake(self.width / 2.f, self.height / 2.f); //center of the curve
UIBezierPath* circlePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:degreesToRadian(270.f) endAngle:DEGREES_TO_RADIANS(269.999f) clockwise:YES]; // go around like the clock
CGFloat strokeStart = 0.f;
// Add the parts of the curve to the view
for (int i = 0; i < segmentValues.count; i++) {
NSNumber *value = [segmentValues objectAtIndex:i];
CGFloat strokeEnd = [value floatValue] / kPercentHundred;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = circlePath.CGPath;
circle.lineCap = kCALineCapButt;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [[colors objectAtIndex:i] CGColor];
circle.lineWidth = kWidhtLine;
circle.zPosition = 1.f;
circle.strokeStart = strokeStart;
circle.strokeEnd = strokeStart + strokeEnd;
[self.layer addSublayer:circle];
UIView *example = [[UIView alloc] init];
example.backgroundColor = kColorRed;
example.size = CGSizeMake(10, 10);
// middle = "(circle.strokeStart + circle.strokeEnd) / 2.f"
// * convert percent to degree
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * 360.f;
// determine the point and add the right offset
example.origin = CGPointMake(cosf(angle) * radius + center.x, sinf(angle) * radius + center.y);
[self addSubview:example];
strokeStart = circle.strokeEnd;
}
What I get see the figure.
What I expect in concept is something similar to the following figure.
There are two key issues:
You calculate the angle via:
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * 360.f;
You then proceed to use that for the cos and sin functions. Those expect radian values, though, so you have to convert this to radians before you use it in those functions. Thus, yielding:
CGFloat angle = degreesToRadian((circle.strokeStart + circle.strokeEnd) / 2.f * 360.f);
(BTW, I don't know if you prefer degreesToRadian or DEGREES_TO_RADIANS, but you should probably use one or the other.)
Or, more simply, you can convert to radians directly:
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * M_PI * 2.0;
You have rotated the circle 90 degrees (presumably so that it would start a "12 o'clock" rather than "3 o'clock"). Well, if you rotate the circle, then you have to rotate angle when you calculate where to put your example view, as well:
example.center = CGPointMake(cosf(angle - M_PI_2) * radius + center.x, sinf(angle - M_PI_2) * radius + center.y);
This yields:
I'm not sure what your goal is. You say you want to create labels, but what you create in your code is generic UIView objects.
Then you are setting a property "origin" on the view, but UIView does not have an origin as far as I know. It has a frame, which has an origin component, but not an origin property. I'm guessing that the code you posted is not actual, running code.
Do you want the labels to be centered on top of the circle? If so, you should both set the text alignment to NSTextAlignmentCenter, and set the center property of the views rather than the frame.origin.
I have a working sample project on github that creates an analog clock and puts labels for the hours around it. You can look at the code for an idea of how to do it:
Analog clock sample project
(The project also shows how to use the new spring physics UIView animation method.)
hmm it i a little late for me ;)
I think you have to use UIView::setCenter this will solve you problem. Then the center of the view is on your circle.
Edit: And it is easier to manipulate the position.

Calculate rotation angle for anchor point

I got stuck with a problem where I need to reposition views to predefined locations.
All views have a UIPanGestureRecognizer and a UIRotationGestureRecognizer and are positioned/rotated inside the controllers view. Upon a certain event the views should move to a new position with a new rotation angle.
Everything works fine but a soon as one of the gesture recognizer was active and thus the anchorPoint has changed repositioning/rotation fails.
Here is my method I try to use to take the shift in the anchorPoint into account.
- (CGPoint)centerPointWithInVisibleAreaForPoint:(CGPoint)point
{
CGPoint anchorP = self.layer.anchorPoint;
anchorP.x -= 0.5;
anchorP.y -= 0.5;
CGRect rect = self.bounds;
CGFloat widthDelta = CGRectGetWidth(self.bounds) * anchorP.x;
CGFloat heightDelta = CGRectGetHeight(self.bounds) * anchorP.y;
CGPoint newCenter = CGPointMake(point.x + widthDelta, point.y + heightDelta);
return newCenter;
}
The controller asks for the corrected center point and sets the center value of the view. Afterwards the rotation transform is set using CGAffineTransformConcat(view.transform, CGAffineTransformMakeRotation(differenceAngle)).
I think the problem is caused by the fact that the predefined target angle is based on a rotation around the center which is obviously different when rotated around a different anchorPoint, but I don't know how to compensate for that.
The only solution I found (and it is after all the most easiest one) is to reset the anchorPoint to 0.5/0.5 and correct the position accordingly.
- (void)resetAnchorPoint
{
if (!CGPointEqualToPoint(self.layer.anchorPoint, CGPointMake(0.5, 0.5))) {
CGFloat width = CGRectGetWidth(self.bounds);
CGFloat height = CGRectGetHeight(self.bounds);
CGPoint newPoint = CGPointMake(width * 0.5, height * 0.5);
CGPoint oldPoint = CGPointMake(width * self.layer.anchorPoint.x, height * self.layer.anchorPoint.y);
newPoint = CGPointApplyAffineTransform(newPoint, self.transform);
oldPoint = CGPointApplyAffineTransform(oldPoint, self.transform);
CGPoint position = self.layer.position;
position.x += (newPoint.x - oldPoint.x);
position.y += (newPoint.y - oldPoint.y);
[CATransaction setDisableActions:YES];
self.layer.position = position;
self.layer.anchorPoint = CGPointMake(0.5, 0.5);
[CATransaction setDisableActions:NO];
}
}

Resources