Piechart not rotating on tap in Coreplot- iOS - core-animation

Please refer to the code for rotating the piechart when any sice is touched:
-(void)pieChart:(CPTPieChart *)plot sliceWasSelectedAtRecordIndex:(NSUInteger)index{
[self rotatePieChart:plot];
}
-(void)rotatePieChart:(CPTPieChart *)plot {
[CATransaction begin];
{
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D transform = CATransform3DMakeRotation(-45, 0, 0, 1);
rotation.toValue = [NSValue valueWithCATransform3D:transform];
rotation.fillMode = kCAFillModeForwards;
rotation.duration = 1.0f;
rotation.cumulative=YES;
rotation.removedOnCompletion = NO;
[plot addAnimation:rotation forKey:#"rotation"];
}
[CATransaction commit];
}
ON first Tap the piechart rotates but on subsequent taps nothing happens.

Instead of rotating the whole plot layer, animate only the startAngle property. I suspect the hit-detection code is getting confused by the rotated coordinate system.

Related

CAShapeLayer animation flash when I set duration very small

I want to realize a round progress, and the progress value can be set dynamically. The code is followed:
- (CGPoint)center {
return CGPointMake(self.view.frame.origin.x + self.view.frame.size.width / 2.0,
self.view.frame.origin.y + self.view.frame.size.height / 2.0);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
CGMutablePathRef roundPath = CGPathCreateMutable();
CGPathAddArc(roundPath, NULL, self.center.x, self.center.y,
20,
2 * M_PI + M_PI_2,
M_PI_2,
YES);
CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
backgroundLayer.frame = self.view.layer.bounds;
backgroundLayer.path = roundPath;
backgroundLayer.strokeColor = [[NSColor blueColor] CGColor];
backgroundLayer.fillColor = nil;
backgroundLayer.lineWidth = 10.0f;
backgroundLayer.lineJoin = kCALineJoinBevel;
[self.view.layer addSublayer:backgroundLayer];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.view.layer.bounds;
pathLayer.path = roundPath;
pathLayer.strokeColor = [[NSColor whiteColor] CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 10.0f;
pathLayer.lineJoin = kCALineJoinBevel;
[self.view.layer addSublayer:pathLayer];
self.pathLayer = pathLayer;
[self start];
}
- (void)start {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 0.01;
pathAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
pathAnimation.toValue = [NSNumber numberWithFloat:self.progress+0.01];
[self.pathLayer setStrokeEnd:self.progress + 0.01];
[pathAnimation setDelegate:self];
[self.pathLayer addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
self.progress += 0.01;
if (self.progress < 1.0) {
[self start];
}
}
I found when I set the duration be 0.1f or even bigger, it will work right.But if I set the duration be 0.01f, the animation will not start from the correct value, it will animate from a bigger value then decrease to the correct value. So the whole animation always flash, anybody got the same question or know why? Thanks very much!
originaluser2 is probably right about the specific cause, but this design is incorrect.
At 60fps, one frame is 0.0167s. You're asking to animate the change in less than a single frame. Each of those animations has its own media timing (kCAMediaTimingFunctionDefault), which means your creating complicated ramp up/ramp down velocities through your animation. And your timing is going to be erratic anyway because you're picking up a little error in every step (animationDidStop can take slightly different amounts of time to run depending on many factors). The whole point of animations is that you don't need to do this kind of stuff. That's why you have an animation engine.
Just animate to progress over the time you want it to take. Don't try to inject many extra animation steps. Injecting steps is what the animation engine does.
CALayer is designed to do most of this stuff for you anyway. You don't need to be creating explicit animations for this; just use implicit. Something like (untested, uncompiled):
- (void)setProgress: (CGFloat)progress {
double velocity = 1.0;
[CATransaction begin];
double delta = fabs(_progress - progress);
[CATransaction setAnimationDuration: delta * velocity];
[self.pathLayer setStrokeEnd: progress];
[CATransaction commit];
}
As Rob says, adding repeated animations isn't the best idea. His solution will probably work out nicely for you. However if you're still insistent on using repeated animations, here's the fix for your current code:
The problem is you're calling this line of code before you add your animation.
[self.pathLayer setStrokeEnd:self.progress + 0.01];
This will create an implicit animation on the layer, and therefore when you come to add your explicit animation – it will cause problems (the flashes you were observing).
The solution is to update the model layer within a CATransaction after you start the animation. You'll also need to set disableActions to YES in order to prevent an implicit animation from being generated.
For example:
- (void)start {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 0.01;
pathAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
pathAnimation.toValue = [NSNumber numberWithFloat:self.progress+0.01];
[pathAnimation setDelegate:self];
[self.pathLayer addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self.pathLayer setStrokeEnd:self.progress + 0.01];
[CATransaction commit];
}
Although, it's also worth noting you could just create your animation by just using a CATransaction, and the implicit animation for strokeEnd.
For example:
- (void)start {
[CATransaction begin];
[CATransaction setAnimationDuration:0.01];
[CATransaction setCompletionBlock:^{
self.progress += 0.01;
if (self.progress < 1.0) {
[self start];
}
}];
[self.pathLayer setStrokeEnd:self.progress + 0.01];
[CATransaction commit];
}
That way you don't have to create a new explicit animation on each iteration.

Animating CAShapeLayer's strokeColor with CABasicAnimation

I've been unsuccessful at animating a flashing stroke on a CAShapeLayer using the answer from this previous thread, and after many searches I can find no other examples of animating the stroke using CABasicAnimation.
What I want to do is have the stroke of my CAShapeLayer pulse between two colors. Using CABasicAnimation for opacity works fine, but the [CABasicAnimation animationWithKeyPath:#"strokeColor"] eludes me, and I'd appreciate any advice on how to successfully implement.
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor blackColor].CGColor;
strokeAnim.toValue = (id) [UIColor purpleColor].CGColor;
strokeAnim.duration = 1.0;
strokeAnim.repeatCount = 0.0;
strokeAnim.autoreverses = NO;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
// CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
// opacityAnimation.fromValue = [NSNumber numberWithFloat:0.0];
// opacityAnimation.toValue = [NSNumber numberWithFloat:1.0];
// opacityAnimation.duration = 1.0;
// opacityAnimation.repeatCount = 0.0;
// opacityAnimation.autoreverses = NO;
// [shapeLayer addAnimation:opacityAnimation forKey:#"animateOpacity"];
Uncommenting the opacity animation results in an expected opacity fade. The stroke animation produces no effect. An implicit strokeColor change animates as expected, but I would like documented confirmation that strokeColor can be explicitly animated using CABasicAnimation.
Update: The specific problem was that shapeLayer.path was NULL. Correcting that fixed the problem.
The code below works great for me. What is the lineWidth of your shapeLayer stroke path? Could that be the issue?
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath * circle = [UIBezierPath bezierPathWithOvalInRect:self.view.bounds];
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.path = circle.CGPath;
shapeLayer.strokeColor =[UIColor blueColor].CGColor;
[shapeLayer setLineWidth:15.0];
[self.view.layer addSublayer:shapeLayer];
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor redColor].CGColor;
strokeAnim.toValue = (id) [UIColor greenColor].CGColor;
strokeAnim.duration = 3.0;
strokeAnim.repeatCount = 0;
strokeAnim.autoreverses = YES;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
}
Let me know if it works for you...

UILabel size change suddenly when parent UIView animate

I want to send a subview out of my super UIView with animation, It works fine but when i tried to change the size during animation any UILabel that i have in my subview suddenly become too small.
here is part of my code
-(void)pushOutScreen:(UIViewController *)pop{
[UIView animateWithDuration:1
delay:0.0
options: UIViewAnimationTransitionFlipFromLeft
animations:^{
CGRect frame = pop.view.frame;
frame.size.height = frame.size.height /4;
frame.size.width = frame.size.width /4;
frame.origin.x = -500;
frame.origin.y = 318;
pop.view.frame = frame;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
note: any UIButton or UIImage that is inside my subview animated well but i have only problem with UILabel.
UIView animation is not good option for doing this, instead of it try CAKeyframeAnimation. This is sample code for scaling UIView:
- (void) scaleView:(UIView *)popView {
CAKeyframeAnimation *animation = [CAKeyframeAnimation
animationWithKeyPath:#"transform"];
animation.delegate = self;
// CATransform3DMakeScale has 3 parameter (x,y,z)
CATransform3D scale1 = CATransform3DMakeScale(1.0, 1.0, 1);
CATransform3D scale2 = CATransform3DMakeScale(0.2, 0.2, 1);
NSArray *frameValues = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:scale1],
[NSValue valueWithCATransform3D:scale2],
nil];
[animation setValues:frameValues];
NSArray *frameTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:1.0],
nil];
[animation setKeyTimes:frameTimes];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1.0;
[popView.layer addAnimation:animation forKey:#"popup"];
}
you can use this after you add your UIView as subView then you can call this method as scale it. for push out a sub view with this method you need to use removeFromSubView after the animation finished.
for knowing that when it finished use
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
[subView removeFromSuperview];
}
I hope it be useful!

Flip, Grow, and Translate Animation

Look at this video of the MLB At Bat app. Basically, I just want to present a modalViewController with the UIModalPresentationFormSheet style and have it grow from another view then flip. Like when you tap on a game in the scoreboard on the MLB app. Anyone know how I can accomplish this?
Thanks
EDIT: My main view is pretty much the same setup as the MLB app. I'm using AQGridView and want the animation to occur when a cell in the grid view is tapped.
EDIT 2: I'd also be open to ditching the UIViewController concept and just using a plain UIView, then replicate the style of UIModalPresentationFormSheet manually if that's easier.
EDIT 3: Okay, forget using a UIViewController to do this, since I haven't gotten any responses, I'll assume it isn't possible. My new question is just how do I replicate the animation in the posted video using just UIView's? So basically, the initial view needs to grow, move, and flip all at the same time.
EDIT 4: I think I have the actual animation figured out, now my only problem is calculating coordinates to feed into CATransform3DTranslate. My view needs to animate pretty much exactly like in the video. It needs to start over another view and animate to the center of the screen. Here's how I'm trying to calculate the coordinates for the view that pops up in the center:
CGPoint detailInitialPoint = [gridView convertPoint:view.frame.origin toView:detailView.frame.origin];
CGPoint detailFinalPoint = detailView.frame.origin;
gridView is the container view of my main view that holds the smaller grid items. view is the specific grid item that we are animating from. And detailView is the view that comes up in the middle of the screen.
You can implement your own transition using a category on UIViewControler.
UIViewController+ShowModalFromView.h
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#interface UIViewController (ShowModalFromView)
- (void)presentModalViewController:(UIViewController *)modalViewController fromView:(UIView *)view;
#end
UIViewController+ShowModalFromView.m
#import "UIViewController+ShowModalFromView.h"
#implementation UIViewController (ShowModalFromView)
- (void)presentModalViewController:(UIViewController *)modalViewController fromView:(UIView *)view {
modalViewController.modalPresentationStyle = UIModalPresentationFormSheet;
// Add the modal viewController but don't animate it. We will handle the animation manually
[self presentModalViewController:modalViewController animated:NO];
// Remove the shadow. It causes weird artifacts while animating the view.
CGColorRef originalShadowColor = modalViewController.view.superview.layer.shadowColor;
modalViewController.view.superview.layer.shadowColor = [[UIColor clearColor] CGColor];
// Save the original size of the viewController's view
CGRect originalFrame = modalViewController.view.superview.frame;
// Set the frame to the one of the view we want to animate from
modalViewController.view.superview.frame = view.frame;
// Begin animation
[UIView animateWithDuration:1.0f
animations:^{
// Set the original frame back
modalViewController.view.superview.frame = originalFrame;
}
completion:^(BOOL finished) {
// Set the original shadow color back after the animation has finished
modalViewController.view.superview.layer.shadowColor = originalShadowColor;
}];
}
#end
This can easily be changed to use whatever animated transition you want; for your's, you might want to use a CA3DTransform. Hope this helps!
To do a flip, grow, and translate animation you can use the following code:
- (void)animate {
int newX, newY; //New position
int newW, newH; //New size
[UIView animateWithDuration:someDuration delay:someDelay animations:^{
yourView.transform = CATransform3DMakeRotation(M_PI_2,1.0,0.0,0.0); //flip halfway
yourView.frame = CGRectMake(newX/2, newY/2, newW/2, newH/2);
} completion:^{
while ([yourView.subviews count] > 0)
[[yourView.subviews lastObject] removeFromSuperview]; // remove all subviews
// Add your new views here
[UIView animateWithDuration:someDuration delay:someDelay animations:^{
yourView.transform = CATransform3DMakeRotation(M_PI,1.0,0.0,0.0); //finish the flip
yourView.frame = CGRectMake(newX, newY, newW, newH);
} completion:^{
// Flip completion code here
}];
}];
}
Hope this helps!
OK, I figured it out. Here's what I did:
CGFloat duration = 0.8;
/*
//Detail View Animations
*/
CATransform3D initialDetailScale = CATransform3DMakeScale(view.frame.size.width/vc.view.frame.size.width, view.frame.size.height/vc.view.frame.size.height, 1.0);
CATransform3D initialDetailTransform = CATransform3DRotate(initialDetailScale, -M_PI, 0.0, -1.0, 0.0);
CATransform3D finalDetailScale = CATransform3DMakeScale(1.0, 1.0, 1.0);
CATransform3D finalDetailTransform = CATransform3DRotate(finalDetailScale, 0.0, 0.0, -1.0, 0.0);
NSMutableArray *detailAnimations = [NSMutableArray array];
CABasicAnimation *detailTransform = [CABasicAnimation animationWithKeyPath:#"transform"];
detailTransform.fromValue = [NSValue valueWithCATransform3D:initialDetailTransform];
detailTransform.toValue = [NSValue valueWithCATransform3D:finalDetailTransform];
detailTransform.duration = duration;
[detailAnimations addObject:detailTransform];
CABasicAnimation *detailFadeIn = [CABasicAnimation animationWithKeyPath:#"opacity"];
detailFadeIn.fromValue = [NSNumber numberWithFloat:1.0];
detailFadeIn.toValue = [NSNumber numberWithFloat:1.0];
detailFadeIn.duration = duration/2;
detailFadeIn.beginTime = duration/2;
[detailAnimations addObject:detailFadeIn];
CABasicAnimation *detailMove = [CABasicAnimation animationWithKeyPath:#"position"];
detailMove.fromValue = [NSValue valueWithCGPoint:vc.view.layer.position];
detailMove.toValue = [NSValue valueWithCGPoint:self.view.layer.position];
detailMove.duration = duration;
[detailAnimations addObject:detailMove];
CAAnimationGroup *detailGroup = [CAAnimationGroup animation];
[detailGroup setAnimations:detailAnimations];
[detailGroup setDuration:duration];
detailGroup.removedOnCompletion = NO;
detailGroup.fillMode = kCAFillModeForwards;
detailGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[vc.view.layer addAnimation:detailGroup forKey:#"anim"];
/*
//Grid Item View Animations
*/
CATransform3D initialGridScale = CATransform3DMakeScale(1.0, 1.0, 1.0);
CATransform3D initialGridTransform = CATransform3DRotate(initialGridScale, 0.0, 0.0, 1.0, 0.0);
CATransform3D finalGridScale = CATransform3DMakeScale(vc.view.frame.size.width/view.frame.size.width, vc.view.frame.size.height/view.frame.size.height, 1.0);
CATransform3D finalGridTransform = CATransform3DRotate(finalGridScale, M_PI, 0.0, 1.0, 0.0);
NSMutableArray *gridAnimations = [NSMutableArray array];
CABasicAnimation *gridTransform = [CABasicAnimation animationWithKeyPath:#"transform"];
gridTransform.fromValue = [NSValue valueWithCATransform3D:initialGridTransform];
gridTransform.toValue = [NSValue valueWithCATransform3D:finalGridTransform];
gridTransform.duration = duration;
[gridAnimations addObject:gridTransform];
CABasicAnimation *gridFadeOut = [CABasicAnimation animationWithKeyPath:#"opacity"];
gridFadeOut.fromValue = [NSNumber numberWithFloat:0.0];
gridFadeOut.toValue = [NSNumber numberWithFloat:0.0];
gridFadeOut.duration = duration/2;
gridFadeOut.beginTime = duration/2;
[gridAnimations addObject:gridFadeOut];
CABasicAnimation *gridMove = [CABasicAnimation animationWithKeyPath:#"position"];
gridMove.fromValue = [NSValue valueWithCGPoint:view.layer.position];
gridMove.toValue = [NSValue valueWithCGPoint:[self.view.layer convertPoint:self.view.layer.position toLayer:gridView.layer]];
gridMove.duration = duration;
[gridAnimations addObject:gridMove];
CAAnimationGroup *gridGroup = [CAAnimationGroup animation];
[gridGroup setAnimations:gridAnimations];
gridGroup.duration = duration;
gridGroup.fillMode = kCAFillModeForwards;
gridGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
gridGroup.removedOnCompletion = NO;
[view.layer addAnimation:gridGroup forKey:#"anim"];
I'm doing this with an item in an AQGridView. In my code sample view is the instance of AQGridViewCell that I am animating. And vc.view is my detail UIViewController/UIView that I'm displaying.
I would highly recommend taking a look at this post.
You shouldn't need to actually handle all this animation yourself.
If you use the UIView class method transitionFromView:toView:duration:options:completion: and passing in UIViewAnimationOptionTransitionFlipFromLeft or UIViewAnimationOptionTransitionFlipFromRight -- whichever way you want the animation to flip.
I have implemented the same thing as shown in the MLB app using this method. Your from view would be the cell in the grid and the to view would be the thing that would on the "back" of the cell.
Hope this will reduce the overall code you'll need.
I would use a UICollectionView with a custom UICollectionViewCell and animate in with its delegate call... Just an example for others to pick at.
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:
(NSIndexPath *)indexPath
{
[UIView beginAnimations:#"showImage" context:Nil];
CGRect cellFrame = cell.frame;
CGRect imgFram = cell.imageView.frame;
[UIView setAnimationDuration:0.8];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:cell
cache:YES];
cellFrame.size = CGSizeMake(200, 200);
cellFrame.origin.y = 10;
cellFrame.origin.x = 45;
cell.frame = cellFrame;
imgFram.size = CGSizeMake(200, 200);
cell.imageView.frame = imgFram;
[collectionView bringSubviewToFront:cell];
[UIView commitAnimations];
}

iphone sdk MapView update users location

I've successfully put together a mapview with a pin annotation representing the users current position that updates at a set interval. When the location manager updates the user's position, the pin annotation disappears and reappears at the new location. Has anyone played with getting the current user's GPS location to update through the use of an animation, like what is done in Apple's offical mapping application? If so, I'd love some pointers to get this to work. Thanks!
Solution 1:
First, you need to make your view controller implement MKMapViewDelegate if it doesn't already.
Then, implement this method:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
MKAnnotationView *aV;
for (aV in views) {
CGRect endFrame = aV.frame;
aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - 230.0, aV.frame.size.width, aV.frame.size.height);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.45];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[aV setFrame:endFrame];
[UIView commitAnimations];
}
}
Add your annotations to the MapView and when they are added, this delegate method will be called and will animate the pins from top to bottom as they are added.
The values for timing and positioning can be changed a little bit but I've tweaked it to make it look best/closest to the traditional drop (as far as I've tested).
Solution 2:
Alternatively, if you're making a MKAnnotationView subclass, you can use didMoveToSuperview to trigger the animation. The following does a drop that ends in a slight 'squish' effect
#define kDropCompressAmount 0.1
#implementation MyAnnotationViewSubclass
...
- (void)didMoveToSuperview {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = 0.4;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400, 0)];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"transform"];
animation2.duration = 0.10;
animation2.beginTime = animation.duration;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation2.toValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(0, self.layer.frame.size.height*kDropCompressAmount, 0), 1.0, 1.0-kDropCompressAmount, 1.0)];
animation2.fillMode = kCAFillModeForwards;
CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:#"transform"];
animation3.duration = 0.15;
animation3.beginTime = animation.duration+animation2.duration;
animation3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation3.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animation3.fillMode = kCAFillModeForwards;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:animation, animation2, animation3, nil];
group.duration = animation.duration+animation2.duration+animation3.duration;
group.fillMode = kCAFillModeForwards;
[self.layer addAnimation:group forKey:nil];
}
Hope this helps!
PK