add an animation to bar plot does not take effect visually - core-animation

I am a newer to core plot. And recently I decide to add bar growth animation to my plot. so I followed the instruction searched here to implement the animation. Now I come up with the problem: the animation seemed not to take effect.The delegate: didStopAnimate:(BOOL) returns no matter how long I set the duration of the animation with flag false.
One more thing to state: in the graph view, I put a scrollview under the real graph hosting view. does that affect Core animation to work?
the plot appeared after a navigation from the table view to itself. here is some of my codes:
if ([plot_type isEqualToString:#"bar"]) {
CPTBarPlot *plot = [[CPTBarPlot alloc] init];
//id
plot.identifier = [d objectForKey:#"title"];
plot.title = [d objectForKey:#"title"];
plot.lineStyle = barLineStyle;
plot.barCornerRadius = 1.2;
//set color
CPTGradient *gradient = [CPTGradient gradientWithBeginningColor:[CPTColor colorWithCGColor:color_begin] endingColor:[CPTColor colorWithCGColor:color_end]];
gradient.angle = 90.0f;
CPTFill *fill = [CPTFill fillWithGradient:gradient];
plot.fill = fill;
//bar width
double width = [[d objectForKey:#"width"] doubleValue]==0.0?0.6:[[d objectForKey:#"width"] doubleValue];
plot.barWidth = CPTDecimalFromFloat(width);
plot.barsAreHorizontal = NO;
//need manually stacked
if (need_stack && !is_first) {
plot.barBasesVary = YES;
} else {
plot.barBasesVary = NO;
}
//data source
plot.dataSource = self;
//delegate
plot.delegate = self;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
[anim setDuration:2.0f];
anim.toValue = [NSNumber numberWithFloat:1];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.removedOnCompletion = NO;
anim.delegate = self;
anim.fillMode = kCAFillModeForwards;
[plot addAnimation:anim forKey:(NSString *)plot.identifier];
[graph addPlot:plot toPlotSpace:plotSpace];
}
Thanks for your helps in advance.

The transform property of a CALayer is CATransform3D struct, not a scalar value. You should wrap it using the NSValue method +valueWithCATransform3D:.

Related

iOS: I want to continuously rotate an image, but it doesn't work

Have the code from several examples on stackoverflow. But my image doesn't rotate..any idea why? It is displayed on the screen, but fixed..
-(UIImageView *)movablePropeller
{
if(!_movablePropeller){
_movablePropeller = [[UIImageView alloc] initWithFrame:self.frame];
_movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
[self addSubview:_movablePropeller];
}
return _movablePropeller;
}
- (void)startPropeller
{
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotation.fromValue = [NSNumber numberWithFloat:0.0f];
rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
rotation.cumulative = true;
rotation.duration = 0.7f; // Speed
rotation.repeatCount = INFINITY; // Repeat forever. Can be a finite number.
[self.movablePropeller.layer removeAllAnimations];
[self.movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
}
(and of course I would love to have it in a seperate thread, not to block the UI)

AVVideoCompositionCoreAnimationTool not adding all CALayers

Okay, this one has me completed stumped. I'm happy to post other code if you need it but I think this is enough. I cannot for the life of me figure out why things are going wrong. I'm adding CALayers, which contain images, to a composition using AVVideoCompositionCoreAnimationTool. I create an NSArray of all the annotations (see interface below) I want to add and then add them to the animation layer with an enumerator. No matter how many, as far as I can tell, annotations are in the array, the only ones that end up in the outputted video are the ones added by the last loop. Can someone spot what I'm missing?
Here's the interface for the annotations
#interface Annotation : NSObject// <NSCoding>
#property float time;
#property AnnotationType type;
#property CALayer *startLayer;
#property CALayer *typeLayer;
#property CALayer *detailLayer;
+ (Annotation *)annotationAtTime:(float)time ofType:(AnnotationType)type;
- (NSString *)annotationString;
#end
And here's the message that creates the video composition with the animation.
- (AVMutableVideoComposition *)createCompositionForMovie:(AVAsset *)movie fromAnnotations:(NSArray *)annotations {
AVMutableVideoComposition *videoComposition = nil;
if (annotations){
//CALayer *imageLayer = [self layerOfImageNamed:#"Ring.png"];
//imageLayer.opacity = 0.0;
//[imageLayer setMasksToBounds:YES];
Annotation *ann;
NSEnumerator *enumerator = [annotations objectEnumerator];
CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(0, 0, movie.naturalSize.width, movie.naturalSize.height);
CALayer *videoLayer = [CALayer layer];
videoLayer.frame = CGRectMake(0, 0, movie.naturalSize.width, movie.naturalSize.height);
[animationLayer addSublayer:videoLayer];
// TODO: Consider amalgamating this message and scaleVideoTrackTime:fromAnnotations
// Single loop instead of two and sharing of othe offset variables
while (ann = (Annotation *)[enumerator nextObject]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = 3; // TODO: Three seconds is the currently hard-coded display length for an annotation, should this be a configurable option after the demo?
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.beginTime = time;
// animation.beginTime = AVCoreAnimationBeginTimeAtZero;
ann.startLayer.opacity = 0.0;
ann.startLayer.masksToBounds = YES;
[ann.startLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.startLayer];
ann.typeLayer.opacity = 0.0;
ann.typeLayer.masksToBounds = YES;
[ann.typeLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.typeLayer];
ann.detailLayer.opacity = 0.0;
ann.detailLayer.masksToBounds = YES;
[ann.detailLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.detailLayer];
}
videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:movie];
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:animationLayer];
}
return videoComposition;
}
I want to stress that the video outputs correctly and I do get the layers appearing at the right time, just not ALL the layers. Very confused and would greatly appreciate your help.
So I was fiddling around trying to figure out what might have caused this and it turns out that it was caused by the layers hidden property being set to YES. By setting it to NO, all the layers appear but then they never went away. So I had to change the animation's autoreverses property to YES and halve the duration.
So I changed the code to this:
while (ann = (Annotation *)[enumerator nextObject]){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = 1.5; // TODO: Three seconds is the currently hard-coded display length for an annotation, should this be a configurable option after the demo?
animation.repeatCount = 0;
animation.autoreverses = YES; // This causes the animation to run forwards then backwards, thus doubling the duration, that's why a 3-second period is using 1.5 as duration
animation.removedOnCompletion = NO;
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.beginTime = time;
// animation.beginTime = AVCoreAnimationBeginTimeAtZero;
ann.startLayer.hidden = NO;
ann.startLayer.opacity = 0.0;
ann.startLayer.masksToBounds = YES;
[ann.startLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.startLayer];
ann.typeLayer.hidden = NO;
ann.typeLayer.opacity = 0.0;
ann.typeLayer.masksToBounds = YES;
[ann.typeLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.typeLayer];
ann.detailLayer.hidden = NO;
ann.detailLayer.opacity = 0.0;
ann.detailLayer.masksToBounds = YES;
[ann.detailLayer addAnimation:animation forKey:#"animateOpacity"];
[animationLayer addSublayer:ann.detailLayer];
}

Change CALayer color while animating

APPROACH 1
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
[CATransaction begin];
{
[CATransaction setAnimationDuration:15];//Dynamic Duration
[CATransaction setCompletionBlock:^{
}];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.fromValue = #0;
animation.toValue = #1;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[self.pathLayer addAnimation:animation forKey:animationKey];
}
[CATransaction commit];
I have added CAShapeLayer (pathLayer) in my view and I want it to animate around the view with stroke effect, the code above does the job but my problem is to change color in 3 equal proportions. So what I am assuming is to repeat the above code 3 times and change the following lines in respective order.
for 1st
animation.fromValue = #0;
animation.toValue = #(1/3);
animation.timeOffset = 0;
for 2nd
animation.fromValue = #(1/3);
animation.toValue = #(2/3);
animation.timeOffset = 0;// I don't know how to exactly set this active local
time since the duration which is currently 15 is dynamic can be 30 or 10.
for 3rd
animation.fromValue = #(2/3);
animation.toValue = #(3);
animation.timeOffset = 0;// Active local time- Not sure how and which value to set
APPROACH 2
Instead of 3 transactions with offset technique lets start 2nd transaction when 1st completes and 3rd when 2nd. But the fraction of time that is taken to start the new animation when one is completed a lag/jerk is visible.
APPROACH 3
SubClass CAShapeLayer
By doing SubClass, the drawInContext method is called only once, and if some extra property is added and it is changed the drawInContext method is called repeatedly and this way the layer color can be changed after specific progress period of time.
But overriding the drawInContext method doesn't serve the purpose.
Any Suggestions ? I don't want to implement NSTimer separately.
I'm not 100% clear on what you want here, but if the goal is just for the whole stroke to change color as it draws, but in three discrete stages, then I would propose adding the following. I cooked up these examples in a default "Single View Application" template. I've got a button set up with its action pointing at -doStuff:. If the whole stroke color were to change, it might look something like this:
To produce that, the code looked like:
#implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.strokeEnd = 0.0;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];// Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(#"Done"); }];
const double portion = 1.0 / ((double)numSegments);
NSMutableArray* values = [NSMutableArray arrayWithCapacity: numSegments];
NSMutableArray* times = [NSMutableArray arrayWithCapacity: numSegments + 1];
for (NSUInteger i = 0; i < numSegments; i++)
{
[values addObject: (__bridge id)[[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor]];
[times addObject: #(i * portion)];
}
[times addObject: #(1.0)]; // Have to add this, otherwise the last value wont get used.
{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: #"strokeColor"];
animation.keyTimes = times;
animation.values = values;
animation.calculationMode = kCAAnimationDiscrete;
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: #"strokeColor"];
}
{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath: #"strokeEnd"];
animation.fromValue = #(0);
animation.toValue = #(1);
animation.removedOnCompletion = NO;
animation.timeOffset = 0;
animation.fillMode = kCAFillModeForwards;
[mLayer addAnimation: animation forKey: #"strokeEnd"];
}
}
[CATransaction commit];
}
#end
Alternately, if the goal is to have three different segments of the stroke, all with different colors, that's a little more complicated, but can still be done with the same basic principals. One thing to note is that, without custom drawing, your CAShapeLayers can't have more than one stroke color (AFAIK), so you'll need to break this up into several sublayers.
This next example puts a shape layer into the view and then adds the sublayers for each part of the stroke and sets up the animation such that it appears theres a single, multi-color stroke being drawn, where each segment is a separate color. Here's roughly what it looked like:
Here's the code:
#implementation MyViewController
{
CAShapeLayer* mLayer;
}
- (IBAction)doStuff:(id)sender
{
const NSUInteger numSegments = 3;
const CFTimeInterval duration = 2;
[mLayer removeFromSuperlayer];
mLayer = [[CAShapeLayer alloc] init];
mLayer.frame = CGRectInset(self.view.bounds, 100, 200);
mLayer.fillColor = [[UIColor purpleColor] CGColor];
mLayer.lineWidth = 12.0;
mLayer.lineCap = kCALineCapSquare;
mLayer.path = [[UIBezierPath bezierPathWithRect: mLayer.bounds] CGPath]; // This can be whatever.
[self.view.layer addSublayer: mLayer];
[CATransaction begin];
{
[CATransaction setAnimationDuration: duration];//Dynamic Duration
[CATransaction setCompletionBlock:^{ NSLog(#"Done"); }];
const double portion = 1.0 / ((double)numSegments);
for (NSUInteger i = 0; i < numSegments; i++)
{
CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
strokePart.fillColor = [[UIColor clearColor] CGColor];
strokePart.frame = mLayer.bounds;
strokePart.path = mLayer.path;
strokePart.lineCap = mLayer.lineCap;
strokePart.lineWidth = mLayer.lineWidth;
// These could come from an array or whatever, this is just easy...
strokePart.strokeColor = [[UIColor colorWithHue: i * portion saturation:1 brightness:1 alpha:1] CGColor];
strokePart.strokeStart = i * portion;
strokePart.strokeEnd = (i + 1) * portion;
[mLayer addSublayer: strokePart];
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath: #"strokeEnd"];
NSArray* times = #[ #(0.0), // Note: This works because both the times and the stroke start/end are on scales of 0..1
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(1.0) ];
NSArray* values = #[ #(strokePart.strokeStart),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(strokePart.strokeEnd) ];
animation.keyTimes = times;
animation.values = values;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[strokePart addAnimation: animation forKey: #"whatever"];
}
}
[CATransaction commit];
}
#end
I'm not sure I've exactly understood what you were going for, but hopefully one of these is helpful.

How to Change NSTableColumn width with Animation

I have a NSScrollView and a NSTableView with some NSTableColumn in it.
When I push a button,I change the NSScrollView'width with Animation.
Here is my code:
NSDictionary *myScrollViewDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
myScrollView,NSViewAnimationTargetKey,
[NSValue valueWithRect:myScrollViewStartFrame],NSViewAnimationStartFrameKey,
[NSValue valueWithRect:myScrollViewEndFrame],NSViewAnimationEndFrameKey, nil];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:#[myScrollViewDictionary]];
animation.delegate = self;
animation.duration = 0.2;
[animation setAnimationBlockingMode:NSAnimationBlocking];
[animation startAnimation];
This works fine.
But I want to change each Column'width in the meanwhile with the same Animation,the problem is NSTableColumn is not a NSView, so I can not change its frame.Now I only can change Column'width without any Animation.
somebody have ideas?
I'm afraid you would probably have to create your own custom animation that does everything manually. To animate the column, here is a general idea:
- (IBAction)animateColumn:(id)sender
{
NSTableColumn *tableColumn; // = set your table column
CGFloat initialWidth = [tableColumn width];
CGFloat finalWidth; // = whatever
dispatch_async(dispatch_get_global_queue(0,0), {
double animationTime = 2; //seconds
double timeElapsed = 0; //approximate measure; probably good enough
while (timeElapsed<animationTime)
{
double t = timeElapsed/animationTime;
CGFloat width = t*finalWidth + (1-t)*initialWidth;
dispatch_async(dispatch_get_main_queue(), ^{
[tableColumn setWidth:width];
});
NSDate *future = [NSDate dateWithTimeIntervalSinceNow:0.02];
[NSThread sleepUntilDate:future];
timeElapsed += 0.02;
}
dispatch_async(dispatch_get_main_queue(), ^{
[tableColumn setWidth:finalWidth];
});
});
}
I guess you can adapt this to do what you want. Perhaps you can start this animation at the same time you start the other animation (using the same time for both, changing 2 to 0.2 in my example).

how to remove the shadow for the label added to pie plot using core plot libraru in iphone sdk

I had added shadow for pie plot but I'm getting shadow for the label data also. I want to remove that shadow. Please help me.
Thanks in advance.
piePlot.dataSource = self;
piePlot.pieRadius = 65.0;
piePlot.pieInnerRadius = 35.0;
piePlot.shadowColor = [[UIColor blackColor]CGColor];
piePlot.shadowRadius = 3.0;
piePlot.shadowOffset = CGSizeMake(8,-3);
piePlot.shadowOpacity = 1.0;
piePlot.identifier = #"Current Year Credits By Type";
piePlot.startAngle = M_PI_4;
piePlot.sliceDirection = CPTPieDirectionClockwise;
piePlot.borderLineStyle = [CPTLineStyle lineStyle];
piePlot.sliceLabelOffset = 10.0;
-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index
{
CPTTextLayer *newLayer = nil;
static CPTMutableTextStyle *whiteText = nil;
if ( !whiteText )
{
whiteText = [[CPTMutableTextStyle alloc] init];
whiteText.color = [CPTColor blackColor];
}
if ( [plot isKindOfClass:[CPTPieChart class]] )
{
NSString *str = [NSString stringWithFormat:#"%#",[pieChartData1 objectAtIndex:index]];
newLayer = [[[CPTTextLayer alloc] initWithText:str style:whiteText] autorelease];
}
return newLayer;
}
Use Core Plot's CPTShadow class instead of the CALayer shadow properties.
CPTMutableShadow *blackShadow = [CPTMutableShadow shadow];
blackShadow.shadowOffset = CGSizeMake(8,-3);
blackShadow.shadowColor = [CPTColor blackColor];
piePlot.shadow = blackShadow;