I'm re-introducing myself to iOS programming and am running into a seemingly simple problem. I have been following this example about how to rotate a image according to the user's heading. The crucial part is:
float heading = -1.0f * M_PI * newHeading.magneticHeading / 180.0f;
arrowImage.transform = CGAffineTransformMakeRotation(heading);
The image is indeed rotated but the center doesn't seem to be the anchor point for the rotation. I've been googling but can't figure it out. Could someone point me in the right direction?
Here's a screenshot in case it isn't clear: in the initial state, the label is centered in the circle and the white square is centered in the image:
This code works for my:
UIImage *img = [UIImage imageNamed:#"Image.png"];
UIImageView *imageToMove = [[UIImageView alloc] initWithImage:img];
CATransform3D rotationTransform = CATransform3DMakeRotation(1.0f * M_PI, 0, 0, 1.0);
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotationAnimation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
rotationAnimation.duration = 0.6f;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = FLT_MAX;
[imageToMove.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[self.view addSubview:imageToMove];
Swift port of Greg's code
let rotationTransform: CATransform3D = CATransform3DMakeRotation(.pi, 0, 0, 1.0);
let rotationAnimation = CABasicAnimation(keyPath: "transform");
rotationAnimation.toValue = NSValue(caTransform3D: rotationTransform)
rotationAnimation.duration = 0.6;
rotationAnimation.isCumulative = true;
rotationAnimation.repeatCount = Float.infinity;
progressArc.layer.add(rotationAnimation, forKey:"rotationAnimation")
try this code for your case
var angle=0;
self.btnImage.center=CGPointMake(self.btnImage.center.x, self.btnImage.center.y);
self.btnImage.transform=CGAffineTransformMakeRotation (angle);
angle+=0.15;
keep the above one into method, use the time interval or call the method when ever you want to change the angle of the image.
Related
I'm trying to implement image rotation in my code and running a couple of tests. Since I don't know much about the math of how rotation is performed, I just followed some instructions that I'd found on the internet and implemented my code.
I found out that a whole row or column of 1-pixel around the edge is lost everytime I rotate the image. (more than M_PI degree at a time)
This is not obvious when I host the image as a UIImage object, but you can see it when you save the UIImage as a file.
Here's my test code link:
https://github.com/asldkfjwoierjlk/UIImageRotationTest/tree/master
I don't understand that this loss happens. Did I miss something? Or is it something mathematically inevitable?
Here's the rotation method that I implemented if you are interested.
- (UIImage*)rotateImg:(UIImage*)srcImg
{
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0, srcImg.size.width, srcImg.size.height)];
CGFloat rotationInDegree = ROTATION_DEGREE;
CGAffineTransform t = CGAffineTransformMakeRotation(rotationInDegree);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
// set opaque option to NO to leave the alpha channel intact.
// Otherwise, there's no point of saving to either png or jpg.
UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, rotatedSize.width / 2.0f, rotatedSize.height / 2.0f);
CGContextRotateCTM(context, rotationInDegree);
[srcImg drawInRect:CGRectMake(-srcImg.size.width / 2.0f, -srcImg.size.height / 2.0f, srcImg.size.width, srcImg.size.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
you can handle the UIImageView as a View. This code works perfectly for me!
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [rotatedViewBox.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
[rotatedViewBox.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
Happy coding!
Some year ago i have used this code for realize a chronometer animation.
Was a cute animation but doesn't work with iOS8>
A circle should appears and lose a pie every 0.1sec but The animation doesn't start. It works on my iPhone 5 iOS 7.1
I tried to solve it but after 2 hours i have no solution.
Can someone with more experience with CABasicAnimation help me?
THANK YOU.
This is the code:
//Animazione CRONOMETRO
-(void) startCronometro{
//SetTime
counterStart = TIME;
[sfondoCronometro setImage:[UIImage imageNamed:#"cronoStart.png"]];
maskLayer = [CAShapeLayer layer];
CGFloat maskHeight = sfondoCronometro.frame.size.height;
CGFloat maskWidth = sfondoCronometro.frame.size.width;
CGPoint centerPoint;
centerPoint = CGPointMake(sfondoCronometro.frame.size.width/2, (sfondoCronometro.frame.size.height/2));
//Make the radius of our arc large enough to reach into the corners of the image view.
CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;
//Don't fill the path, but stroke it in black.
maskLayer.fillColor = [[UIColor clearColor] CGColor];
maskLayer.strokeColor = [[UIColor blackColor] CGColor];
maskLayer.lineWidth = 25;
CGMutablePathRef arcPath = CGPathCreateMutable();
//Move to the starting point of the arc so there is no initial line connecting to the arc
CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);
//Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
CGPathAddArc(arcPath, nil, centerPoint.x, centerPoint.y, radius/2, 3*M_PI/2, -M_PI/2, NO);
maskLayer.path = arcPath;//[aPath CGPath];//arcPath;
//Start with an empty mask path (draw 0% of the arc)
maskLayer.strokeEnd = 0;
CFRelease(arcPath);
//Install the mask layer into out image view's layer.
sfondoCronometro.layer.mask = maskLayer;
//Set our mask layer's frame to the parent layer's bounds.
sfondoCronometro.layer.mask.frame = sfondoCronometro.layer.bounds;
//Create an animation that increases the stroke length to 1, then reverses it back to zero.
CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
swipe.duration = TIME;
NSLog(#"TIME: %f", swipe.duration);
swipe.delegate = self;
// [swipe setValue:#"string" forKey:#"key"];
swipe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
swipe.fillMode = kCAFillModeForwards;
swipe.removedOnCompletion = NO;
swipe.toValue = [NSNumber numberWithFloat: 1.0];
[maskLayer addAnimation: swipe forKey: #"strokeEnd"];
}
I seem to remember that at some point the function of CGPathAddArc changed, and depending on the values of your start and end angle, you had to flip the clockwise flag on the call. Try using clockwise = YES.
I just did a little testing in an app of mine and confirmed this. For the angles you're using, clockwise NO works for iOS <= 7, but fails for iOS >=8.
Switch the last parameter from NO to YES.
I'm trying to do a 360 spin of a view but at a 45 degree tilt.
Like this
I can't figure out for to do it.
So far I've managed this
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(0);
animation.toValue = #(2 * M_PI);
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
Which spins it on it's y axis, but what I want is for this Y axis to be tilted 45 degree before it's spun.
First, you should see the link below which explains that the differences between "frame" and "bounds".
UIView frame, bounds and center
And now, here is my answer.
/* add the 4 lines below */
self.transform = CGAffineTransformMakeRotation(M_PI_4);
self.layer.bounds = CGRectMake(0.0f, 0.0f, 60.0f, 200.0f);
self.layer.position = CGPointMake(150.0f, 300.0f);
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(0);
animation.toValue = #(2 * M_PI);
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
try this;
CABasicAnimation* animation = [CABasicAnimation
animationWithKeyPath:#"transform"];
CATransform3D rtfm = CATransform3DMakeRotation(2 * M_PI , 1.0f, 1.0f, 0.0f);
rtfm.m34 = -0.0015f;
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animation.toValue = [NSValue valueWithCATransform3D:rtfm];
animation.duration = 1;
[self.layer addAnimation:animation forKey:#"rotation"];
1- Change the anchor point to the upper right edge
self.someView.layer.anchorPoint = CGPointMake(1.0, 0.5);
2- rotate the view by 45 or 35 degrees
CGFloat radians = (M_PI/180) * (-35);
self.someView.transform = CGAffineTransformRotate(self.someView.transform, radians);
3- rotate or flip your view by X axis
CGFloat radians = (M_PI/180) * (180);
[UIView animateWithDuration:1 animations:^{
self.someView.layer.transform = CATransform3DRotate(self.someView.layer.transform,radians , 1, 0.0, 0.0);
} completion:^(BOOL finished) {
if(finished) {
}
}];
Works like a charm :)
Trying to animatie an ellipse masked on a UIView to be scale transformed remaining in the center position.
I have found CALayer - CABasicAnimation not scaling around center/anchorPoint, and followed by adding a bounds property to the maskLayer CAShapeLayer however, this ends with the mask being positioned in the left corner with only 1/4 of it showing. I would like the mask to remain within the center of the screen.
#synthesize maskedView;
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Next"];
vc.view.frame = CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height);
vc.view.layer.bounds = self.view.layer.bounds;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, maskRect);
[maskLayer setPath:path];
CGPathRelease(path);
vc.view.layer.mask = maskLayer;
[self.view addSubview:vc.view];
maskedView = vc.view;
[self startAnimation];
}
Animation...
-(void)startAnimation
{
maskedView.layer.mask.bounds = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
maskedView.layer.mask.anchorPoint = CGPointMake(.5,.5);
maskedView.layer.mask.contentsGravity = #"center";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.duration = 1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = [NSNumber numberWithFloat:1.0f];
animation.toValue = [NSNumber numberWithFloat:5.0f];
[maskedView.layer.mask addAnimation:animation forKey:#"animateMask"];
}
Update
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
You can create a scale around the center of the object instead of (0, 0) like this:
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D tr = CATransform3DIdentity;
tr = CATransform3DTranslate(tr, self.bounds.size.width/2, self.bounds.size.height/2, 0);
tr = CATransform3DScale(tr, 3, 3, 1);
tr = CATransform3DTranslate(tr, -self.bounds.size.width/2, -self.bounds.size.height/2, 0);
scale.toValue = [NSValue valueWithCATransform3D:tr];
So we start out with the "identity" transform (which means 'no transform'). Then we translate (move) to the center of the object. Then we scale. Then we move back to origo so we're back where we started (we only wanted to affect the scale operation).
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
I am not sure why, but CAShapeLayer does not set the bounds property to the layer. In my case, that's was the problem.
Try setting the bounds. That should work.
I did something like this for building the initial circle
self.circle = [CAShapeLayer layer];
CGRect roundedRect = CGRectMake(0, 0, width, width);
self.circle.bounds = roundedRect;
UIBezierPath *start = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:width/2];
[self.circle setPath:start.CGPath];
self.circle.position = CGPointMake(whatever suits you);
self.circleView.layer.mask = self.circle;
[self.circleView.layer.mask setValue: #(1) forKeyPath: #"transform.scale"];
And something like this for scaling it
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scale.fromValue = [self.circleView.layer.mask valueForKeyPath:#"transform.scale"];
scale.toValue = #(100);
scale.duration = 1.0;
scale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.circleView.layer.mask setValue:scale.toValue forKeyPath:scale.keyPath];
[self.circleView.layer.mask addAnimation:scale forKey:scale.keyPath];
PD: AnchorPoint is by default (.5,.5) according to apple documentations...but we all know that does not mean anything right?
Hope it helps!!
I have written following function with many added tweaks and options, could be useful for many others.
func layerScaleAnimation(layer: CALayer, duration: CFTimeInterval, fromValue: CGFloat, toValue: CGFloat) {
let timing = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
let scaleAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timing)
scaleAnimation.duration = duration
scaleAnimation.fromValue = fromValue
scaleAnimation.toValue = toValue
layer.add(scaleAnimation, forKey: "scale")
CATransaction.commit()
}
Note: Don't forget to update size after animation completion, because CATransaction reverts back to actual size.
I have the following code that rotates a CALayer by -45degrees on the Y axis:
#define D2R(x) (x * (M_PI/180.0))
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DMakeRotation(D2R(-45), 0, 1.0, 0);
transform.m34 = -1.0 / 850;
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
The animation works, except it ends with no perspective - ignoring my m34 setting if I'm understanding things correctly.
Halfway through:
At the end:
What am I doing wrong?
Animation only affects the appearance of the view during the animation. It doesn't get applied to the view after the animation ends. You need to do this yourself. I'm guessing something like this right after adding the animation will work:
self.layer.transform = transform;
You can do this right away, as the animation will hide it until the animation completes.
Try this :
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -10 / 850.0;
transform = CATransform3DRotate(transform, D2R(-45), 0, 1.0, 0);
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
And end the effect is like this :