background flashes during image swap cross disolve, CATransition - objective-c

Currently I have a CATransistion that should swap out the _backgroundImageView.image with a new one. It works great... And works quickly, however the previous image will glitch during this transition randomly... Aka flash the full image for one frame somewhere in the duration of the transition and then finish the transition.
Here is the code:
[CATransaction begin];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.duration = 0.3;
transition.delegate = self;
[transition setValue:#"swipe" forKey:#"tag"];
[_backgroundImageView.layer addAnimation:transition forKey:nil];
_backgroundImageView.image = [_backgroundImages objectAtIndex:pageControl.currentPage];
[CATransaction commit];
If I add this before the code then the glitch doesnt happen but other UI stuff gets choppy because the image comparison seems to block the main thread (which it has to as the animation is a main thread thing too)
if ([UIImagePNGRepresentation(_backgroundImageView.image) isEqualToData:UIImagePNGRepresentation([_backgroundImages objectAtIndex:pageControl.currentPage])]) {
return;
}
BTW flickering doesn't happen on the device.... at least so far I can see... so maybe this question is moot... I have seen crashes during transitions... but that might be another issue.

Related

animationDidStop method called immediately

I've never had this issue come up before. my animationDidStop method is being called before the animation actually completes. animationDidStart gets called first, but then animationDidStop is called immediately after. I tried to handle this using an animation completion block, but it called the animation completed immediately still. Anybody run across this before? I really could use a bit of help. THANK YOU.
-James
CODE:
-(void) runAnimation {
//Create an animation that rotates the tile
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
[animation setDuration:6];
[animation setFromValue:[NSNumber numberWithFloat:0]];
[animation setToValue:[NSNumber numberWithFloat:0.5*M_PI]];
[animation setDelegate:self];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[[self.view viewWithTag:100].layer addAnimation:animation forKey:#"solutionRotate"];
}
-(void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
if (theAnimation == [[self.view viewWithTag:100].layer animationForKey:#"solutionRotate"]){
//test
NSLog (#"test");
}
}
If the layer is not part of any layer tree then the animation will end immediately since there is nothing to actually animate on screen. Make sure the animated view is added to a visible view hierarchy.

Other transition styles using segue

I've started programming for iOS only for a few weeks, so I don't know how it was done before, but I'd like to use other transition styles for my segues. Like the one where the screen flips, giving you the impression that the destination view controller was on the back of the first one. I suppose I have to subclass UIStoryboardSegue, but apart from that, I have no idea where to go from there.
Thanks for your time!
You can use CATransition within a custom Segue to achieve any kind of transition. Here is sample code.
-(void)perform {
__block UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
__block UIViewController *destinationController = (UIViewController*)[self destinationViewController];
CATransition* transition = [CATransition animation];
transition.duration = .25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromLeft; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[sourceViewController.navigationController.view.layer addAnimation:transition
forKey:kCATransition];
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
}
You visit this link for more details http://blog.jambura.com/2012/07/05/custom-segue-animation-left-to-right-using-catransition/
Ok I didn't look far enough. Select the segue you want to customize, and there's a "transition" style option.

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.

Using NSViewAnimation with NSAnimationEaseOut weird behaviour

In one of my views that can be dragged, if the user drops it at an inappropriate place, it simply drops back to the original frame that was recorded when the mouse went down. This has been working fine for a few days with no animation, but I've decided to introduce animation:
-(void)dropToFrameOrigin:(NSPoint)newFrameOrigin animated:(BOOL)animated {
if (animated) {
NSRect newFrameRect = [self frame];
newFrameRect.origin = newFrameOrigin;
NSDictionary *animationInfo = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
[NSValue valueWithRect:[self frame]], NSViewAnimationStartFrameKey,
[NSValue valueWithRect:newFrameRect], NSViewAnimationEndFrameKey,
nil];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:animationInfo]];
[animation setDuration:0.2];
[animation setAnimationCurve:NSAnimationLinear];
[animation startAnimation];
[animation release];
} else {
[self setFrameOrigin:newFrameOrigin];
}
}
This is working with NSAnimationLinear, and the default NSAnimationEaseInOut, but what I actually want is merely the sense of deceleration, which I assume NSAnimationEaseOut is supposed to do. For some reason, when I use that however, the view snaps immediately to the frame it should end at, then animates sliding back to where the cursor dropped it (the start frame), then snaps back to (what should be) the end frame.
All the other modes (NSAnimationLinear, NSAnimationEaseIn, NSAnimationEaseInOut) seem to work just fine. Am I misunderstanding the purpose of NSAnimationEaseOut?
For some reason you have to flip the start and end frames to get NSAnimationEastOut to work. Plus, you need to set an animation delegate so you can be notified when the animation ends, where you must then manually set the view's frame to the original end frame.

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.