Rotate NSButton clockwise with animation - objective-c

I'm trying to rotate an NSButton clockwise until a user manually interrupts it. Here's the code I'm using to accomplish this. I know it used to work at some point. Any idea how to fix it? Thanks in advance!
CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
a.fromValue = [NSNumber numberWithFloat:0];
a.toValue = [NSNumber numberWithFloat:-M_PI*2];
[self.reloadButton.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
a.duration = 2.0; // seconds
a.repeatCount = HUGE_VAL;
[self.reloadButton.layer addAnimation:a forKey:nil];

Your code works fine for me as long as I set reloadButton.wantsLayer = YES;
From Enabling Core Animation Support in Your App
In iOS apps, Core Animation is always enabled and every view is backed by a layer. In OS X, apps must explicitly enable Core Animation support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against
this framework only if they use Core Animation interfaces
explicitly.)
Enable layer support for one or more of your NSView objects

Try this
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:(M_PI)];
rotation.duration = 0.2; // Speed
rotation.repeatCount = 1; // Repeat forever. Can be a finite number.
[yourButton.layer addAnimation:rotation forKey:#"Spin"];
[yourButton.layer setZPosition:100];
yourButton.transform = CGAffineTransformMakeRotation(M_PI_2);
You can change/set duration, repeatCount and toValue to your convenient.

[EDIT1]
After seeing that this is for an NS versus a UI button, there are a couple of options:
1) Use an NSTimer and the proper rotation routine for you OSX version (see the link below)
http://digerati-illuminatus.blogspot.com/2009/09/how-do-you-rotate-nsbutton-nstextfield.html
2) If you are using OSX 10.5 or above CoreAnimation was supported, and the below should actually be supported for NSButtons.
Wiki Link
http://en.wikipedia.org/wiki/Core_Animation
[ORIGINAL]
Try this code instead:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:5000000];
CGAffineTransform rr=CGAffineTransformMakeRotation(5000000);
reloadButton.transform=CGAffineTransformConcat(reloadButton.transform, rr);
[UIView commitAnimations];
Here is a link to a SO question that shows both methods
UIView Rotation

Related

Xcode Animations complete instantly - CAAnimations call didStop - flag = FALSE

I'm getting some weird bugs. I thought I had a handle on this. I copied code from one of my other projects. I'm wondering if it could be something to do with my custom class.
I have a view controller initialize my custom class called "TitleCardView"
Inside there I have a bunch of animations like this one:
CGPoint startPointb = borderMaskLayer.position;
CGPoint endPointb = CGPointMake(borderMaskLayer.position.x, borderMaskLayer.position.y-1000);
CABasicAnimation* bmoveAnim = [CABasicAnimation animationWithKeyPath:#"position"];
bmoveAnim.delegate=self;
[bmoveAnim setValue:#"borderMaskAnim1" forKey:#"id"];
bmoveAnim.fromValue = [NSValue valueWithCGPoint:startPointb];
bmoveAnim.toValue = [NSValue valueWithCGPoint:endPointb];
bmoveAnim.duration = 1;
[bmoveAnim setBeginTime:CACurrentMediaTime()+.4];
bmoveAnim.fillMode = kCAFillModeForwards;
bmoveAnim.removedOnCompletion = NO;
bmoveAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[self.borderLayer.mask addAnimation:bmoveAnim forKey:#"position"];
[self.borderWhiteLayer.mask addAnimation:bmoveAnim forKey:#"position"];
The animation works fine but when I try to implement AnimationDidStop{, as soon as the view loads, all the animations get logged by the delegate method with the FALSE (did not finish) flag.
I added a button and tried to use:
[UIView animateWithDuration:6 animations:^{
continueButton.alpha = 1.0f;
}];
and this code with the delay parameter....
Same problem. As soon as the view loads, its like the animation gets run immediately with a duration of 0.
Are you not supposed to add animations in your init method? I feel like there must be a rule I'm breaking that I don't know about.
This code does work:
this button is the last thing in the init method
continueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[continueButton addTarget:self
action:#selector(titleNext)
forControlEvents:UIControlEventTouchUpInside];
[continueButton setTitle:#"Click to Continue" forState:UIControlStateNormal];
continueButton.frame = CGRectMake(0.0, 390.0, 320.0, 75.0);
[self addSubview:continueButton];
continueButton.alpha = 1.0;
then this is the method it calls
-(void)titleNext{
// proceeds to the motto page from the title page
[UIView animateWithDuration:.6 animations:^{
continueButton.alpha = 0.0f;
}];
}
So can anyone tell me why my animations are acting weird???
I recreated the project and found the animationDidStop delegate worked when the animations were created in -(void)viewDidAppear{ instead of -(void)viewDidLoad{
For swift it works if the animations are in
override func viewDidAppear(_ animated: Bool)

iOS 7 - CoreAnimation not working for UICollectionViewCell

I have a simple animation of shaking of cell icons in UICollectionView, similar to spring board edit mode. Animation was working well in iOS 6, but not working in iOS 7.
Here is the sample code.
- (void)startQuivering
{
CABasicAnimation *quiverAnim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
float startAngle = (-1) * M_PI/180.0;
float stopAngle = -startAngle;
quiverAnim.fromValue = [NSNumber numberWithFloat:startAngle];
quiverAnim.toValue = [NSNumber numberWithFloat:3 * stopAngle];
quiverAnim.autoreverses = YES;
quiverAnim.duration = 0.12;
quiverAnim.repeatCount = HUGE_VALF;
float timeOffset = (float)(arc4random() % 100)/100 - 0.50;
quiverAnim.timeOffset = timeOffset;
CALayer *layer = self.layer;
[layer addAnimation:quiverAnim forKey:#"quivering"];
}
Similarly Stoping the quivering animation.
- (void)stopQuivering
{
CALayer *layer = self.layer;
[layer removeAnimationForKey:#"quivering"];
}
Calling these methods in applyLayoutAttributes: method in my custom UICollectionViewCell class, depending on long press gesture and related flag
I am unable to figure out the issue, so I need help from the developers.
Finally Solved the issue. Its similar to this question How to refresh UICollectionViewCell in iOS 7? -applyLayoutAttributes: was not getting called properly, could solve the issue by overriding isEqual: method in custom UICollectionViewLayoutAttributes subclass and calling super -applyLayoutAttributes:.

Using CAMediaTimingFunction with block-based UIView animations

The View Programming Guide for iOS tells us that block-based animations are the way forward, as opposed to the now almost deprecated begin/commit style animations:
Note: If you are writing an application for iOS 4 or later, you should use the block-based methods for animating your content instead. For information on how to use those methods, see “Starting Animations Using the Block-Based Methods.”
But now I'm in a situation where I need to use custom timing functions CAMediaTimingFunction so I've resorted to using CATransactions and CABasicAnimations. These classes uses the same semantical language as the deprecated UIView animations style with methods like [CATransaction begin] and [CATransaction commit]. It just feels odd in the middle of apps where everything else is block-based.
Is there a way to combine concepts like the CAMediaTimingFunctions with block-based animations?
Update 1:
A piece of example code that I would like to 'blockify' looks like this:*
[CATransaction begin];
{
[CATransaction setValue:[NSNumber numberWithFloat:3.0f] forKey:kCATransactionAnimationDuration];
CGPoint low = CGPointMake(0.150, 0.000);
CGPoint high = CGPointMake(0.500, 0.000);
[CATransaction begin];
{
CAMediaTimingFunction* perfectIn = [CAMediaTimingFunction functionWithControlPoints:low.x :low.y :1.0 - high.x :1.0 - high.y];
[CATransaction setAnimationTimingFunction: perfectIn];
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeIn.fromValue = [NSNumber numberWithFloat:0];
fadeIn.toValue = [NSNumber numberWithFloat:1.0];
[viewB.layer addAnimation:fadeIn forKey:#"animateOpacity"];
}
[CATransaction commit];
}
[CATransaction commit];
Update 2
I've made an example project for another question of mine that contains the code above. It's on github.
But now I'm in a situation where I need to use custom timing functions CAMediaTimingFunction so I've resorted to using CATransactions and CABasicAnimations. These classes uses the same semantical language as the deprecated UIView animations style with methods like [CATransaction begin] and [CATransaction commit]. It just feels odd in the middle of apps where everything else is block-based.
I think you are misreading the documentation.
Block based animations are the way to do UIView animations. Period. Full stop.
This statement DOES NOT correspond to CoreAnimation. You still have to use begin/commit for CoreAnimation. Don't make the assumption that CA begin and commit are bad, just because a higher level construct (UIView) deprecated begin/commit.
Is there a way to combine concepts like the CAMediaTimingFunctions with block-based animations?
If you need the advanced capabilities of Core Anmiation, such as custom timings, you should use CoreAnimation the way it is intended (with begin/commit, etc.)
If you are trying to animate CALayers, use Core Animation.
If you are doing high-level UIView based animations, use the UIView block-based animations.
Now I'm going to go ahead and admit this looks pretty pointless but it's the quickest thing I could think of to get you a block interface and it does stop you form accidentally leaving off the being/commit
.h
+ (void)transactionWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations;
.m
+ (void)transactionWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations;
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration];
animations();
[CATransaction commit];
}
Usage with your code (assuming you made it a category on UIView)
[UIView transactionWithDuration:3 animations:^{
CGPoint low = CGPointMake(0.150, 0.000);
CGPoint high = CGPointMake(0.500, 0.000);
CAMediaTimingFunction* perfectIn =
[CAMediaTimingFunction functionWithControlPoints:low.x
:low.y
:1.0 - high.x
:1.0 - high.y];
[CATransaction setAnimationTimingFunction: perfectIn];
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeIn.fromValue = [NSNumber numberWithFloat:0];
fadeIn.toValue = [NSNumber numberWithFloat:1.0];
[viewB.layer addAnimation:fadeIn forKey:#"animateOpacity"];
}];

Animating a gaussian blur using core animation?

I'm trying to animate something where it's initially blurry then it comes into focus. I guess it works OK, but when the animation is done it's still a little blurry. Am I doing this wrong?
CABasicAnimation* blurAnimation = [CABasicAnimation animation];
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setDefaults];
[blurFilter setValue:[NSNumber numberWithFloat:0.0] forKey:#"inputRadius"];
[blurFilter setName:#"blur"];
[[self layer] setFilters:[NSArray arrayWithObject:blurFilter]];
blurAnimation.keyPath = #"filters.blur.inputRadius";
blurAnimation.fromValue = [NSNumber numberWithFloat:10.0f];
blurAnimation.toValue = [NSNumber numberWithFloat:1.0];
blurAnimation.duration = 1.2;
[self.layer addAnimation:blurAnimation forKey:#"blurAnimation"];
Your problem is that the animation stops and is automatically removed, but the filter lingers with the tiniest of blur applied.
What you want to do is to remove the blur filter when the animation completes. You need to add a delegate to the CABasicAnimation instance and implement the -[id<CAAnimationDelegate> animationDidStop:finished:] method.
If you let self be the delegate in this case it should be fairly simple, add this line before adding the animation to your layer:
blurAnimation.delegate = self;
And the callback is equally simple:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[[self layer] setFilters:nil];
}
If you're looking for an optimized way to animate a blur then I recommend creating a single blurred image of your view and then fading the blurred image from alpha 0 to 1 over the top of your original view. Seems nice and fast in tests.

Animating Views with Core Animation Layer

I have a NSWindow containing a NSView with 'Wants Core Animation Layer' enabled. The view then contains many NSImageView that use are initially animated into position. When I run the animation, it is extremely sluggish and drops most of the frames. However, if I disable 'Wants Core Animation Layer' the animation works perfectly. I'm going to need the core animation layer but can't figure out how to get it to perform adequately.
Can I do anything to fix the performance issues?
Here is the code:
// AppDelegate
NSRect origin = ...;
NSTimeInterval d = 0.0;
for (id view in views)
{
[view performSelector:#selector(animateFrom:) withObject:origin afterDelay:d];
d += 0.05f;
}
// NSImageView+Animations
- (void)animateFrom:(NSRect)origin
{
NSRect original = self.frame;
[self setFrame:origin];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.20f];
[[self animator] setFrame:original];
[NSAnimationContext endGrouping];
}
It's possible that the NSTimer is killing your performance. Core Animation has rich support for controlling the timing of animations through the CAMediaTiming protocol, and you should take advantage of that in your app. Instead of using the animator proxy and NSAnimationContext, try using Core Animation directly. If you create a CABasicAnimation for each image and set its beginTime, it will delay the start of the animation. Also, for the delay to work the way you want, you must wrap each animation in a CAAnimationGroup with its duration set to the total time of the entire animation.
Using the frame property could also be contributing to the slowdown. I really like to take advantage of the transform property on CALayer in situations like this where you're doing an "opening" animation. You can lay out your images in IB (or in code) at their final positions, and right before the window becomes visible, modify their transforms to the animation's starting position. Then, you just reset all of the transforms to CATransform3DIdentity to get the interface into its normal state.
I have an example in my <plug type="shameless"> upcoming Core Animation book </plug> that's very similar to what you're trying to do. It animates 30 NSImageViews simultaneously with no dropped frames. I modified the example for you and put it up on github. These are the most relevant bits of code with the extraneous UI stuff stripped out:
Transform the layers to their start position
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// ... SNIP ... //
//Start with all of the images at the origin
[CATransaction begin];
[CATransaction setDisableActions:YES];
for (CALayer *imageLayer in [[[self imageContainer] layer] sublayers]) {
CGPoint layerPosition = [layer position];
CATransform3D originTransform = CATransform3DMakeTranslation(20.f - layerPosition.x, -layerPosition.y, 0.f);
[imageLayer setTransform:originTransform];
}
[CATransaction commit];
}
Animate the transform back to the identity
- (IBAction)runAnimation:(id)sender {
CALayer *containerLayer = [[self imageContainer] layer];
NSTimeInterval delay = 0.f;
NSTimeInterval delayStep = .05f;
NSTimeInterval singleDuration = [[self durationStepper] doubleValue];
NSTimeInterval fullDuration = singleDuration + (delayStep * [[containerLayer sublayers] count]);
for (CALayer *imageLayer in [containerLayer sublayers]) {
CATransform3D currentTransform = [[imageLayer presentationLayer] transform];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
anim.beginTime = delay;
anim.fromValue = [NSValue valueWithCATransform3D:currentTransform];
anim.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.fillMode = kCAFillModeBackwards;
anim.duration = singleDuration;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObject:anim];
group.duration = fullDuration;
[imageLayer setTransform:CATransform3DIdentity];
[imageLayer addAnimation:group forKey:#"transform"];
delay += delayStep;
}
}
I also have a video on YouTube of the example in action if you want to check it out.
Did you try to batch everything in a CATransaction?
[CATransaction begin];
for {...}
[CATransaction commit];
CATransaction is the Core Animation mechanism for batching multiple layer-tree operations into atomic updates to the render tree.