I'm trying to drag a CALayer in an iOS app.
As soon as I change its position property it tries to animate to the new position and flickers all over the place:
layer.position = CGPointMake(x, y)
How can I move CALayers instantly? I can't seem to get my head around the Core Animation API.
You want to wrap your call in the following:
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
layer.position = CGPointMake(x, y);
[CATransaction commit];
Swift 3 Extension :
extension CALayer {
class func performWithoutAnimation(_ actionsWithoutAnimation: () -> Void){
CATransaction.begin()
CATransaction.setValue(true, forKey: kCATransactionDisableActions)
actionsWithoutAnimation()
CATransaction.commit()
}
}
Usage :
CALayer.performWithoutAnimation(){
someLayer.position = newPosition
}
You can also use the convenience function
[CATransaction setDisableActions:YES]
as well.
Note: Be sure to read the comments by Yogev Shelly to understand any gotchas that could occur.
As others have suggested, you can use CATransaction.
The problem comes arises because CALayer has a default implicit animation duration of 0.25 seconds.
Thus, an easier (in my opinion) alternative to setDisableActions is to use setAnimationDuration with a value of 0.0.
[CATransaction begin];
[CATransaction setAnimationDuration:0.0];
layer.position = CGPointMake(x, y);
[CATransaction commit];
Combining previous answers here for Swift 4, to clearly make the animation duration explicit...
extension CALayer
{
class func perform(withDuration duration: Double, actions: () -> Void) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
actions()
CATransaction.commit()
}
}
Usage...
CALayer.perform(withDuration: 0.0) {
aLayer.frame = aFrame
}
Related
I have an AVCaptureVideoPreviewLayer on screen. I want to change its position instantly after the user touches a button.
I am able to change its position using the following code, but it is not happening instantly but with some kind of smooth liner transition.
CGRect frame = self.preview.frame;
frame.origin.x = previewLeft;
frame.origin.y = previewTop;
self.preview.frame = frame;
How can I make it to change its position without any kind of intrinsic animation?
CALayer.frame is a computed property, but it cannot be explicitly animated. By setting it, you're also setting position, which is implicitly animated.
You can temporarily suppress all actions, the mechanism by which Core Animation creates its default layer animations:
Swift code:
CATransaction.begin()
CATransaction.setDisableActions(true)
self.preview.frame = frame;
CATransaction.commit()
Objective-C code:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.preview.frame = frame;
[CATransaction commit];
LucasTizma's answer is perfect.
Besides, there is a trick to solve this issue.
Just slow down the window speed:
[(CALayer *)[[[[UIApplication sharedApplication] windows] objectAtIndex:0] layer] setSpeed:0.9];
Don't forget to correct it to 1.0f after your transition.
My transaction starts slow, gets fast, then goes slow.
I need linear speed throughout.
I've found UIViewAnimationOptionCurveLinear but can't find an example of [CATransaction begin]
Here is my code:
[ CATransaction begin ];
if( graph_animation_enable )
[CATransaction setAnimationDuration: graph_animation_seconds ];
else
[CATransaction setAnimationDuration: 0 ];
//[CATransaction setValue : ( id ) kCFBooleanTrue forKey : kCATransactionDisableActions];
graph_CALayer.frame = CGRectMake( left_x, top_y, width, height );
graph_CALayer.backgroundColor = bar_background_color.CGColor;
CAMediaTimingFunction *linearTiming =
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
[CATransaction setAnimationTimingFunction: linearTiming];
[CATransaction commit];
I TRIED THE ANSWER, BUT STILL NON-LINEAR.
Animation starts, but then slows down.
Use CATransaction setAnimationTimingFunction and the timing value of kCAMediaTimingFunctionLinear
The code would look like this:
CAMediaTimingFunction *linearTiming =
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
[CATransaction setAnimationTimingFunction: linearTiming]
With Swift 5, CATransaction has a method called setAnimationTimingFunction(_:). setAnimationTimingFunction(_:) has the following declaration:
class func setAnimationTimingFunction(_ function: CAMediaTimingFunction?)
Sets the timing function used for all animations within this transaction group. [...] This is a convenience method that sets the CAMediaTimingFunction for the value(forKey:) value of the kCATransactionAnimationTimingFunction key.
Therefore, you can use one of the following code snippets in order to set a linear animation for your CATransaction:
CATransaction.begin()
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear))
/* ... */
CATransaction.commit()
CATransaction.begin()
CATransaction.setValue(CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear), forKey: kCATransactionAnimationTimingFunction)
/* ... */
CATransaction.commit()
I've been trying to rotate a button using the following method:
-(IBAction)rotate:(id)sender{
CGPoint pencilCenter = pencil.center;
[pencil setCenter:pencilCenter];
CGFloat floater = 1.0;
[UIView animateWithDuration:0.7 animations:^(void){
[pencil setTransform:CGAffineTransformMakeRotation(floater)];
}];
[UIView animateWithDuration:0.7 animations:^(void){
[pencil setTransform:CGAffineTransformMakeRotation(floater)];
}];
}
This is supposed to make the button do some kind of "shake", then it's supposed to be back in its original position- yet all it does is changing the button's location, moving it to only one side and on another run of the method the button doesn't react at all.
What's the problem with my code?
Thanks!
EDIT 2:
My que is- how do I make a button to do a little shake/wiggle ,e.g. the wiggle app mode when editing sptingboard.
Using this code is giving me rotation to left, smooth animates from left to right then right to left, then rotates to original position. Now, I want this not to just rotate, but do this with an animation, like a wiggle.
[UIView animateWithDuration:0.25
delay:0.0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveLinear | UIViewAnimationOptionAutoreverse)
animations:^ {
pencil.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(30));
pencil.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished){
}
];
Thanks!
Import "QuartzCore/QuartzCore.h" and try this,
CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.delegate = self;
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotation.duration = 1.7;
fullRotation.repeatCount = 2;
[btnTemp.layer addAnimation:fullRotation forKey:#"360"];
Try using CGAffineTransformRotate instead of CGAffineTransformMakeRotation. You can use `CGAffineTransformIdentity1 as the first argument in all calls, so the final transform according to second argument will be applied on the original shape(?) of the frame.
While it didn't perfectly answer the question, Shardul's answer is great to rotate the button (2 full rotations). So here it is, converted to Swift 3:
let fullRotation = CABasicAnimation(keyPath: "transform.rotation")
fullRotation.delegate = self
fullRotation.fromValue = NSNumber(floatLiteral: 0)
fullRotation.toValue = NSNumber(floatLiteral: Double(CGFloat.pi * 2))
fullRotation.duration = 0.5
fullRotation.repeatCount = 2
button.layer.add(fullRotation, forKey: "360")
You will need to import QuartzCore:
import QuartzCore
And your ViewController needs to conform to CAAnimationDelegate:
class ViewController: UIViewController, CAAnimationDelegate {
}
Well, the docs say: "...the specified animations are started immediately on another thread ...". Now, all UIKit code is NOT thread safe. So, maybe it helps if instead calling your UI setters (setTransform, setCenter) from the completion block (which runs in a separate thread) to call them using performSelectorOnMainThread:withObject:.
The documentation for Google Maps for iOS states that:
Call one of several methods that allow you to animate the camera moving to a new location. You can control the duration of the animation with CoreAnimation.
For the life of me, I can't figure out how to control the animation duration. I have tried using UIView animations, like:
[UIView animateWithDuration: 5 animations:^{
GMSCameraPosition *camera = [self newCamera];
self.mapView.camera = camera;
} completion:^(BOOL finished) {
}];
And I have looked at CALayer animations in CoreAnimation. However, I don't know how you would apply a layer animation to the map view.
Can someone point me in the right direction please?
I found the answer ... you can control the animation duration by wrapping one of the animate* methods in a CATransaction, like this:
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 1.0f] forKey:kCATransactionAnimationDuration];
// change the camera, set the zoom, whatever. Just make sure to call the animate* method.
[self.mapView animateToCameraPosition: [self newCamera]];
[CATransaction commit];
for Swift 3.0:
CATransaction.begin()
CATransaction.setValue(1.5, forKey: kCATransactionAnimationDuration)
// your camera code goes here, example:
// mapView.animate(with: update)
CATransaction.commit()
The bigger the value (1.5 in this case), the slower the animation.
Swift 2.0
CATransaction.begin()
CATransaction.setValue(NSNumber(float: 1.0), forKey: kCATransactionAnimationDuration)
// change the camera, set the zoom, whatever. Just make sure to call the animate* method.
CATransaction.commit()
what a pitty that using the same methods you provided there is no way to know if the animation has ended.
Yes I know, there is a CATransaction completion block using this method but it does simply not work! :(
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 1.0f] forKey:kCATransactionAnimationDuration];
[CATransaction setCompletionBlock:^{
// ... whatever you want to do when the animation is complete
}];
[self.googleMapsView animateToCameraPosition:[GMSCameraPosition
cameraWithLatitude:LATITUDE
longitude:LONGITUDE
zoom:ZOOM]];
[CATransaction commit];
And I can't use MapView:didIdle hack to know that the animation has ended because it will not be called if there is no camera position change.
Anyone knows how to detect animateon has ended event?
FOUND A THREAD ABOUT THIS (solved):
CATransaction completion being called immediately
I have a UIView which has several subviews that respond to touch. If I rotate the layer of the superview with some code like this:
[CATransaction begin];
[CATransaction setDisableActions:true];
view.layer.transform = CATransform3DRotate(view.layer.transform, angle, 0, 0, 1);
[CATransaction commit];
... then I can't seem to find the right location of the subviews when responding to touch events... Any ideas?
I found it, worked out beautifully I guess. I needed to convert points between UIViews for doing bezier hit testing:
-(BOOL)containsPoint:(CGPoint)point
{
point = [self.superview.superview convertPoint:point toView:self];
return [self.slicePath containsPoint:point];
}