Strange behaviour of rotation transform for CALayer - ios

I'm using CALayer for interactive book engine and want to rotate CALayer around it center but when I apply rotation transform (using angle which is based on user input) to CALayer it goes crazy: layer stretches and moves in very strange way and disappears. What I do is
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
...
layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
...
[CATransaction commit];
Interestingly, when my angle is M_PI or M_PI/2, or it's scale transform everything is ok, but when its rotation and any other angles layer goes crazy.
Does anybody know what's happening and how to fix it?

Found the reason myself: it was because in parallel I moved layer using .frame instead of .position property: frame property itself is calculated based on transform thus setting it produced that 'crazy' behavior.

Related

CABasicAnimation to path strange behaviour [duplicate]

I am running into an issue when I create an explicit animation to change the value of a CAShapeLayer's path from an ellipse to a rect.
In my canvas controller I setup a basic CAShapeLayer and add it to the root view's layer:
CAShapeLayer *aLayer;
aLayer = [CAShapeLayer layer];
aLayer.frame = CGRectMake(100, 100, 100, 100);
aLayer.path = CGPathCreateWithEllipseInRect(aLayer.frame, nil);
aLayer.lineWidth = 10.0f;
aLayer.strokeColor = [UIColor blackColor].CGColor;
aLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:aLayer];
Then, when I animate the path I get a strange glitch / flicker in the last few frames of the animation when the shape becomes a rect, and in the first few frames when it animates away from being a rect. The animation is set up as follows:
CGPathRef newPath = CGPathCreateWithRect(aLayer.frame, nil);
[CATransaction lock];
[CATransaction begin];
[CATransaction setAnimationDuration:5.0f];
CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:#"path"];
ba.autoreverses = YES;
ba.fillMode = kCAFillModeForwards;
ba.repeatCount = HUGE_VALF;
ba.fromValue = (id)aLayer.path;
ba.toValue = (__bridge id)newPath;
[aLayer addAnimation:ba forKey:#"animatePath"];
[CATransaction commit];
[CATransaction unlock];
I have tried many different things like locking / unlocking the CATransaction, playing with various fill modes, etc...
Here's an image of the glitch:
http://www.postfl.com/outgoing/renderingglitch.png
A video of what I am experiencing can be found here:
http://vimeo.com/37720876
I received this feedback from the quartz-dev list:
David Duncan wrote:
Animating the path of a shape layer is only guaranteed to work when
you are animating from like to like. A rectangle is a sequence of
lines, while an ellipse is a sequence of arcs (you can see the
sequence generated by using CGPathApply), and as such the animation
between them isn't guaranteed to look very good, or work well at all.
To do this, you basically have to create an analog of a rectangle by
using the same curves that you would use to create an ellipse, but
with parameters that would cause the rendering to look like a
rectangle. This shouldn't be too difficult (and again, you can use
what you get from CGPathApply on the path created with
CGPathAddEllipseInRect as a guide), but will likely require some
tweaking to get right.
Unfortunately this is a limitation of the otherwise awesome animatable path property of CAShapeLayers.
Basically it tries to interpolate between the two paths. It hits trouble when the destination path and start path have a different number of control points - and curves and straight edges will have this problem.
You can try to minimise the effect by drawing your ellipse as 4 curves instead of a single ellipse, but it still isn't quite right. I haven't found a way to go smoothly from curves to polygons.
You may be able to get most of the way there, then transfer to a fade animation for the last part - this won't look as nice, though.

Rotate CAShapeLayer around it center without moving postion

I want to rotate a CAShapeLayer with objective c around it center point without moving it around. The CAShapeLayer contain UIBezierPath point of rect. I'm not able to rotate the CAShapeLayer becouse i dont know how. Please show me how tp rotate around it center without moving it postion.
Here is some code that does that:
//Create a CABasicAnimation object to manage our rotation.
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
totalAnimationTime = rotation_count;
rotation.duration = totalAnimationTime;
//Start the animation at the previous value of angle
rotation.fromValue = #(angle);
//Add change (which will be a change of +/- 2pi*rotation_count
angle += change;
//Set the ending value of the rotation to the new angle.
rotation.toValue = #(angle);
//Have the rotation use linear timing.
rotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
/*
This is the magic bit. We add a CAValueFunction that tells the CAAnimation we are modifying
the transform's rotation around the Z axis.
Without this, we would supply a transform as the fromValue and toValue, and for rotations
> a half-turn, we could not control the rotation direction.
By using a value function, we can specify arbitrary rotation amounts and directions, and even
Rotations greater than 360 degrees.
*/
rotation.valueFunction = [CAValueFunction functionWithName: kCAValueFunctionRotateZ];
/*
Set the layer's transform to it's final state before submitting the animation, so it is in it's
final state once the animation completes.
*/
imageViewToAnimate.layer.transform = CATransform3DRotate(imageViewToAnimate.layer.transform, angle, 0, 0, 1.0);
//Now actually add the animation to the layer.
[imageViewToAnimate.layer addAnimation:rotation forKey:#"transform.rotation.z"];
(That code is taken (and simplified) from my github project KeyframeViewAnimations)
In my project I'm rotating the layer of a UIImageView, but the same approach will work for any CALayer type.

Getting a smooth rotation using pan gesture recognizer

I am rotating a CATransform3D layer around y-axis by taking input from UIPanGestureRecognizer:
CGPoint movement = [recognizer translationInView:self];
CGFloat trackSpread = self.bounds.size.width;
CGFloat angle = (35 * abs(movement.x))/trackSpread;
transform = CATransform3DRotate(transform, degToRad(angle), 0, 1, 0);
The view rotates smoothly if the pan is done slowly i.e. receives continuous x values from recognizer but if the panning is at a faster pace i.e. the values receives from recognizer are not continuous I see jumps in the rotation (kind of like the layer is also translating a bit around its rotation point). Would what be the way to always keep the rotation smooth whether panning is done slow or fast?
Stand alone layers have implicit animations that happen automatically just by changing the property. This is what you are seeing. When continuously updating the property it looks like the layer is lagging and falling behind.
If you want to change the property without having the implicit animation you can change it within a transaction that has disabled all actions (a more general name for animations):
[CATransaction begin];
[CATransaction setDisableActions:YES]; // no animations
myLayer.transform = newTransform;
[CATransaction commit];
You can also configure the layer to never look for an animation for a given key path by adding NSNull to its actions dictionary for that key path:
myLayer.actions = #{
#"transform": [NSNull null] // never animate "transform"
};

Apply a Transform to a UIView Itself

I'm trying to apply a perspective transform to my UIView object with the following code:
CATransform3D t = CATransform3DIdentity;
t.m34 = -1.0/1000;
t = CATransform3DRotate(t, angle, 0.0f, 1.0f, 0.0f);
myView.layer.transform = t;
and I don't see any effect at all. I tried other transforms like a simple translation and they don't work either.
However, if I do either of the following two modifications then it will work somewhat but neither satisfies my request:
Change the last line to
myView.layer.sublayerTransform = t;
This sort of works but it only transforms the subviews on myView, not myView itself.
Add an animation code to apply the change instead of directly assign the change to the layer:
CABasicAnimation *turningAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
turningAnimation.toValue = [NSValue valueWithCATransform3D:t];
turningAnimation.delegate = self;
turningAnimation.fillMode = kCAFillModeForwards;
turningAnimation.removedOnCompletion = NO;
[myView.layer addAnimation:turningAnimation forKey:#"turning"];
The thing is that I don't want the animation.
Can anybody point a direction for me?
Thanks!
You should be able to just use myView.transform = CGAffineTransformMakeRotation(angle). It won't animate the property unless you explicitly tell it to using [UIView animateWithDuration:]. Using the UIView transform property will ultimately apply your transformation to the CALayer that backs UIView, so I would make sure to only interact with the transforms on one level (UIView or CALayer).
UIView transform doc:
https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/occ/instp/UIView/transform
CGAffineTransform Doc:
https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGAffineTransform/Reference/reference.html#//apple_ref/c/func/CGAffineTransformMakeRotation
I figured out the problem after test by Krishnan confirmed that layer.transform itself does work without the aid of animation or sublayerTransform. The problem I encountered was caused by an animation that had been applied to my view's layer and was not subsequently removed. Since this animation had a toValue equal to the identity transform, I didn't realize it can actually prevent subsequent non-animated transforms from working. Setting removedOnCompletion = YES (which is default) on the animation solved the mystery.

Animating CAShapeLayer with moving frame

Using core animation layers, I've been trying to implement the following feature. Within a containing superlayer, there are two anchor layers, and another layer that connects the two. The following image should make the situation clear. On the left, the two orange-coloured anchors are marked 'A' and 'B', and the green line connects them. The enclosing layer frames are shown using dotted lines. On the right, the layer hierarchy is shown as I've currently implemented it, where both the anchors and the connection are sublayer of the enclosing superlayer.
Now, what I'm trying to do is allow the anchors to be moved around, and have the connection stay attached. I'm currently updating its frame and path properties exploiting the connection layer's -setFrame: method , using the code shown below:
- (void)setFrame:(CGRect)frame {
CGSize size = frame.size;
CGPoint startPoint = CGPointZero;
if (size.height < 0.0) startPoint.y -= size.height;
CGPathRef oldPath = self.path;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(path, NULL, startPoint.x + size.width, startPoint.y + size.height);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = [CATransaction animationDuration];
animation.timingFunction = [CATransaction animationTimingFunction];
animation.fromValue = (id)oldPath;
animation.toValue = (id)path;
[self addAnimation:animation forKey:#"pathAnimation"];
self.path = path;
CGPathRelease(path);
[super setFrame:frame];
}
Now, this sort of works, but the problem is that the frame (or position + bounds) animation doesn't run in sync with the path animation, causing some jittery effects where the connection's far end momentarily detaches (and some other minor issues to, presumably caused by the same core issue).
I've been toying around with the issue, but only to moderate success. At one point, I set the connection's frame equal to that of the enclosing superlayer, which did have the desired effect (since now the frame doesn't need to be animated any longer). However, I'm worried about the performance of this solution in a context with multiple connections—ie. multiple non-opaque, large-size overlapping layers seems bad?
Would anyone have a better, more elegant solution to this? Thanks!
Since you're only really stretching (scaling) and rotating the connection layer have you considered applying transformations to it instead of manually modifying the frame?
You should be able to calculate the angle of rotation and scaling factor using some basic trigonometry based on the positions of the anchor layers.
Since you are animating the path and not the frame of the layer or path, you will get performance issues after approx. 50 simultaneous animations.
You will achieve high performance only when manipulating the layers implicit animatable properties, therefore its frame and not its content (e.g path), because of gpu acceleration.

Resources