iPhone/xCode - EXC_BAD_ACCESS on 4th run of Animation Code - iphone

This code works for the first 3 times, and every time without fail, it crashes on the 4th run, with a Thread 1: EXC_BAD_ACCESS (code=1, address=0x4). I have researched all afternoon, but have come up with nothing, is it that I am not releasing something?
- (void)animateImageView:(UIImageView *)imageViewForAnimation
{
imageViewForAnimation.alpha = 1.0f;
CGRect imageFrame = imageViewForAnimation.frame;
//Your image frame.origin from where the animation need to get start
CGPoint viewOrigin = imageViewForAnimation.frame.origin;
viewOrigin.y = viewOrigin.y + imageFrame.size.height / 2.0f;
viewOrigin.x = viewOrigin.x + imageFrame.size.width / 2.0f;
imageViewForAnimation.frame = imageFrame;
imageViewForAnimation.layer.position = viewOrigin;
[self.view addSubview:imageViewForAnimation];
// Set up fade out effect
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeOutAnimation setToValue:[NSNumber numberWithFloat:0.3]];
fadeOutAnimation.fillMode = kCAFillModeForwards;
fadeOutAnimation.removedOnCompletion = NO;
// Set up scaling
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.removedOnCompletion = NO;
// Set up path movement
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
//Setting Endpoint of the animation
CGPoint endPoint = CGPointMake(250.0f, 10.0f);
//to end animation in last tab use
//CGPoint endPoint = CGPointMake( 320-40.0f, 480.0f);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, viewOrigin.x, viewOrigin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, viewOrigin.y, endPoint.x, viewOrigin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, resizeAnimation, nil]];
group.duration = 0.7f;
group.delegate = self;
//[group setValue:#"groupAnimation" forKey:#"animationName"];
[group setValue:imageViewForAnimation forKey:#"imageViewBeingAnimated"];
[imageViewForAnimation.layer addAnimation:group forKey:#"savingAnimation"];
[imageViewForAnimation release];
}

Remove the release.
My advice works because the object imageViewForAnimation was a parameter of your method and nothing in your method is adding a retain to the object. So it makes sense that the imageViewForAnimation object's retain count was being decremented unnecessarily. Only release when you have retained. Keep those in the same scope.
Even better, use ARC
ARC you just go to the menu bar in Xcode, Edit > Refactor > Convert to Objective-C ARC... then follow along carefully. It will remove retains and releases from Obj-C stuff.

Related

Nice zoom-fade transition between controllers like in new Facebook, Gmail apps

Recently more and more apps are using new type of transition between views.
It's hard to define it but the transition looks like the preview view is fading out, going lower (downward translation) and scale down a little - all simultanously.
It's really subtle and elegant.
You can observe this transition in slow motion in Facebook app - find somebody's picture on the wall, tap to view it and then drag the fullscreen image down slowly and you will notice that the wall is rising from the back - fading and scaling a little bit. That's the transition.
Transition also envolves statusbar fading.
This transition is also present in Gmail 2.0 when you open settings from the left pane.
I think there is a certain framework or prepared method for this because more and more apps have this implemented.
But maybe I'm also wrong because I see some minor differences in some apps in the transition's trajectory - eg. Gmail uses a little carousel effect, but facebook just scales down to the middle.
Anyway it appears to be a new trend.
I'm looking for some reference or framework or know-how about implementing that sort of transitions.
Thanks for any useful stuff.
Try this link.I think this is the thing you are exactly looking for https://github.com/kentnguyen/KNSemiModalViewController
I first saw it used in the National Geographic apps... have a try with these two methods, the first 'drops it back' the second 'brings it back'. It's worked well for me in the past, just make a few adjustments to suit your needs.
- (void)dropItBack:(id)sender
{
// Position
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
posAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(160, 284)];//center point
// Opacity
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.toValue = [NSNumber numberWithFloat:0.5f];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.toValue = [NSNumber numberWithDouble:0.8];
// Dramaticism of the Z rotation
// A lower number is more dramatic
float distance = 1000;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.f / -distance;
// Rotate Back
CABasicAnimation *rockBack = [CABasicAnimation animationWithKeyPath:#"transform"];
rockBack.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, 1.f, 0.f, 0.f)];
rockBack.beginTime = 0.f;
// Rotate forward
trans.m34 = 0.f;
CABasicAnimation *rockForward = [CABasicAnimation animationWithKeyPath:#"transform"];
rockForward.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, -1.f, 0.f, 0.f)];
rockForward.beginTime = 0.25f;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.5f;
animationGroup.repeatCount = 0.f;
animationGroup.removedOnCompletion = NO;
animationGroup.autoreverses = NO;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animationGroup setAnimations:[NSArray arrayWithObjects:rockBack, rockForward, scaleAnimation, posAnimation, opacityAnimation, nil]];
[self.navigationController.view.layer addAnimation:animationGroup forKey:nil];
popsheet = [UIButton buttonWithType:UIButtonTypeCustom];
[popsheet setBackgroundColor:[UIColor blueColor]];
[popsheet setFrame:CGRectMake(0, 580, 320, 400)];
[popsheet addTarget:self action:#selector(bringItForward:) forControlEvents:UIControlEventTouchUpInside];
[self.tabBarController.view addSubview:popsheet];
[UIView animateWithDuration:0.3 animations:^{
[popsheet setFrame: CGRectMake(0, 180, 320, 400)];
}];
}
- (void)bringItForward:(id)sender
{
// Position
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
posAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(160, 284)];
// Opacity
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.toValue = [NSNumber numberWithFloat:1.f];
// Scale
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.toValue = [NSNumber numberWithDouble:1.f];
// Dramaticism of the Z rotation
// A lower number is more dramatic
float distance = 1000;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.f / distance;
// Rotate back
CABasicAnimation *rockBack = [CABasicAnimation animationWithKeyPath:#"transform"];
rockBack.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, 1.f, 0.f, 0.f)];
rockBack.beginTime = 0.f;
// Rotate forward
trans.m34 = 0.f;
CABasicAnimation *rockForward = [CABasicAnimation animationWithKeyPath:#"transform"];
rockForward.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(trans, M_PI_4, -1.f, 0.f, 0.f)];
rockForward.beginTime = 0.25f;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.5f;
animationGroup.repeatCount = 0.f;
animationGroup.removedOnCompletion = NO;
animationGroup.autoreverses = NO;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animationGroup setAnimations:[NSArray arrayWithObjects:posAnimation, rockBack, rockForward, opacityAnimation, nil]];
[self.navigationController.view.layer addAnimation:animationGroup forKey:nil];
[UIView animateWithDuration:0.3 animations:^{
[popsheet setFrame: CGRectMake(0, 580, 320, 400)];
}];
}
I think you should try this library:
https://github.com/michaelhenry/MHFacebookImageViewer

Creating itunes store style "jump" animation

I am creating a bookmarking feature for my app, I'd like to show the user what happens in a way similar to itunes store, when you buy something it jumps to tabBar. I once watched some WWDC video that explained this, but can't remember how to do it. Any idea where I should start looking for?
You can take a snapshot of the view you want to animate, then create an image layer, then use Core Animation to animate that to the tab bar. Here's the code I use to do that:
- (void)animateSnapshotOfView:(UIView *)view toTab:(UINavigationController *)navController
{
NSUInteger targetTabIndex = [self.tabBarController.viewControllers indexOfObject:navController];
NSUInteger tabCount = [self.tabBarController.tabBar.items count];
// AFAIK there's no API (as of iOS 4) to get the frame of a tab bar item, so guesstimate using the index and the tab bar frame.
CGRect tabBarFrame = self.tabBarController.tabBar.frame;
CGPoint targetPoint = CGPointMake((targetTabIndex + 0.5) * tabBarFrame.size.width / tabCount, CGRectGetMidY(tabBarFrame));
targetPoint = [self.window convertPoint:targetPoint fromView:self.tabBarController.tabBar.superview];
UIGraphicsBeginImageContext(view.frame.size);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect frame = [self.window convertRect:view.frame fromView:view.superview];
CALayer *imageLayer = [CALayer layer];
imageLayer.contents = (id)image.CGImage;
imageLayer.opaque = NO;
imageLayer.opacity = 0;
imageLayer.frame = frame;
[self.window.layer insertSublayer:imageLayer above:self.tabBarController.view.layer];
CGMutablePathRef path = CGPathCreateMutable();
CGPoint startPoint = imageLayer.position;
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddCurveToPoint(path,NULL,
startPoint.x + 100, startPoint.y,
targetPoint.x, targetPoint.y - 100,
targetPoint.x, targetPoint.y);
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
positionAnimation.path = path;
CGPathRelease(path);
CABasicAnimation *sizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
sizeAnimation.fromValue = [NSValue valueWithCGSize:imageLayer.frame.size];
sizeAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(50, 50)];
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.fromValue = [NSNumber numberWithFloat:0.75];
opacityAnimation.toValue = [NSNumber numberWithFloat:0];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:positionAnimation, sizeAnimation, opacityAnimation, nil];
animationGroup.duration = 1.0;
animationGroup.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animationGroup.delegate = self;
[animationGroup setValue:imageLayer forKey:#"animatedImageLayer"];
[imageLayer addAnimation:animationGroup forKey:#"animateToTab"];
}

Call custom function after arriving first caanimation point

I want to slide in a uiimage from right (out of screen) to the middle and then from the middle to the left (out of screen). When the image arrived (first point of the animation path) in the middle I want to call a custom function. How can I realize that?
thanks for help.
Thats what I tried, but its lagging a little bit in the middle (beginning of the second animation). I think its better to have one animation
my code:
- (void) animateFromRightToMiddlePath {
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 3;
pathAnimation.repeatCount = 1;
pathAnimation.delegate = self;
[pathAnimation setValue:#"rightToMiddle" forKey:#"AnimationType"];
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, x + (picture.size.width/2), 250);
CGPathAddLineToPoint(curvedPath, NULL, (picture.size.width/2), 250);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
imageView = [[UIImageView alloc] initWithImage:picture];
imageView.tag = 1;
imageView.frame = CGRectMake(1, 1, picture.size.width, picture.size.height);
[self addSubview:imageView];
[imageView.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
}
- (void) animateFromMiddleToLeftPath {
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 3;
pathAnimation.repeatCount = 1;
pathAnimation.delegate = self;
[pathAnimation setValue:#"middleToLeft" forKey:#"AnimationType"];
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, (picture.size.width/2), 250);
CGPathAddLineToPoint(curvedPath, NULL, -(picture.size.width/2), 250);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
[imageView.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{
NSString *aniType = [theAnimation valueForKey:#"AnimationType"];
if ([aniType isEqualToString:#"rightToMiddle"]) {
[self animateFromMiddleToLeftPath];
// CUSTOM FUNCTION CALL
}
if ([aniType isEqualToString:#"middleToLeft"]) {
// [self animateFromRightToMiddlePath];
}
}
A way to model this kind of interaction is the following:
your image moves through the screen according to its own logic (a fixed animation, you drag it, etc);
you define a timer so that at each tick a handler function is called; this handler has the responsibility (in your specific case) of checking the position of the image;
when the position is what you want, the handler fires your other function.
This could seem complex, but it is not really. It allows you to solve this problem in a modular and reusable way, and it is inspired by how games deal with this kind of problem.

CABasicAnimation Problem

So, I have read in the docs, that use of blocks like
beginAnimation
commitAnimation
is discouraged from os4.0.
So I have tried to get my code to work by using CABasicAnimation. I want to achieve, that an image's frame is resized from its thumbnail size, somewhere within my view, to a full width position e.g. (0, 120, 320, 240) - on my iPhone.
What I have so far:
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:1.0] forKey:kCATransactionAnimationDuration];
CABasicAnimation *scalingAnimation;
scalingAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
scalingAnimation.duration=1.0/2;
scalingAnimation.autoreverses=YES;
scalingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
scalingAnimation.fromValue=[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
scalingAnimation.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeScale(4, 4, 1)];
[b.layer addAnimation:scalingAnimation forKey:#"scaling"];
[CATransaction commit];
My nextstep would be to first try to move the image to a centered position then scale it to the correct size. However, I doubt I'm doin it the right way. Can anyone comment on my code/approach.... is there a better way?
Am pretty sure you have solved this by now, but in any case.
You shouldn't need the [CATransaction begin]; and [CATransaction commit];
The simplest way that I have found to do this kind of thing is to use CAAnimationGroup and and add the animations one by one.
An example would be
CABasicAnimation *scaleX = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
//this is not used, as the group provides the duration
scaleX.duration = duration;
scaleX.autoreverses = NO;
scaleX.toValue = [NSNumber numberWithFloat:1.0];
scaleX.fromValue = [NSNumber numberWithFloat:scaleFrom];
scaleX.fillMode = kCAFillModeForwards;
scaleX.removedOnCompletion = NO;
CABasicAnimation *scaleY = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
//this is not used, as the group provides the duration
scaleY.duration = duration;
scaleY.autoreverses = NO;
scaleY.toValue = [NSNumber numberWithFloat:1.0];
scaleY.fromValue = [NSNumber numberWithFloat:scaleFrom];
scaleY.fillMode = kCAFillModeForwards;
scaleY.removedOnCompletion = NO;
//add in the translation animations
NSMutableArray* animationsArray = [NSMutableArray arrayWithObjects:scaleX,
scaleY,
//and any other animations you want in the group
nil];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 1.0/2;
animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn];
animationGroup.animations = animationsArray;
animationGroup.delegate = self;
animationGroup.removedOnCompletion = YES;
animationGroup.fillMode = kCAFillModeForwards;
[animationGroup setValue:#"imageTransform" forKey:#"AnimationName"];
[b.layer addAnimation:animationGroup forKey:#"imageTransform"];
There are a few gotchas though. Animations are purely visual, so before running the animations, set your eventual view frame.
You will notice that the scales end at 1, this is to ensure that you dont end up with a scaled image layer. Instead we start it scaled and bring it back to normal.
Translations should be done in the same way.
Hope this helps
//in Event Method Copy Below code to place the image to the center
CABasicAnimation* positionAnimation;
positionAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
positionAnimation.fromValue = [NSValue valueWithCGPoint:imageView.layer.position];
positionAnimation.toValue = [NSValue valueWithCGPoint:centerPointofView];
positionAnimation.duration = 2.0;
positionAnimation.fillMode = kCAFillModeForwards;
positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
positionAnimation.removedOnCompletion = NO;
positionAnimation.delegate = self;
[imageView.layer addAnimation:positionAnimation forKey:#"positionAnimation"];
// For Scale After image is in center copy below code
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{
CAAnimation *animation = [imageView.layer animationForKey:#"positionAnimation"];
if (animation == theAnimation)
{
CABasicAnimation* scaleAnimation;
scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1];
scaleAnimation.toValue = [NSNumber numberWithFloat:4.0];
scaleAnimation.duration = 2.2;
scaleAnimation.fillMode = kCAFillModeForwards;
scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
scaleAnimation.removedOnCompletion = NO;
scaleAnimation.delegate = self;
[imageView.layer addAnimation:scaleAnimation forKey:#"scaleAnimation"];
}
else
{
//if you want changes to image should remain forever
//place your code for scale & transform here
....................
//now simple remove animation from layer
[imageView.layer removeAllAnimations];
}
}
Cant you use
[UIView animateWithDuration:2.0
animations:^{
//animations
}
completion:^(BOOL finished){
// completion methods
}];

iphone view animation problem

I'm using following code to animate a view. It basically rotates the view by 225 degrees angle.
[viewToOpen.layer removeAllAnimations];
viewToOpen.hidden = NO;
viewToOpen.userInteractionEnabled = NO;
if (viewToOpen.layer.anchorPoint.x != 0.0f) {
viewToOpen.layer.anchorPoint = CGPointMake(0.0f, 0.5f);
viewToOpen.center = CGPointMake(viewToOpen.center.x - viewToOpen.bounds.size.width/2.0f, viewToOpen.center.y);
}
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.duration = duration;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CATransform3D endTransform = CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(225));
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.delegate = self;
theGroup.duration = duration;
[theGroup setValue:[NSNumber numberWithInt:viewToOpen.tag] forKey:#"viewToOpenTag"];
theGroup.animations = [NSArray arrayWithObjects:transformAnimation, nil];
theGroup.removedOnCompletion = NO;
[viewToOpen.layer addAnimation:theGroup forKey:#"flipViewOpen"];
But the problem is that, at the end of animation, the view is coming back to original position. I would like to keep the view in same position even after animation completes. How can I do it?
I believe you're experiencing the same issue as can be seen in this question and this one. You either need to set fillMode to kCAFillModeForwards or set the transform of the layer when the animation has been completed.