Performance issues with accelerometer - ios

I have an accelerometer that I use to shift layers of UIImageviews slightly, to get some depth perception.
Here is the code I use to instantiate the animations and accelerometer inside my viewdidload method.
UIAccelerometer *accelerator = [UIAccelerometer sharedAccelerometer];
accelerator.delegate = self;
accelerator.updateInterval = 0.1f;
animateLayer0 = [CABasicAnimation animationWithKeyPath:#"position"];
animateLayer1 = [CABasicAnimation animationWithKeyPath:#"position"];
animateLayer2 = [CABasicAnimation animationWithKeyPath:#"position"];
animateLayer0.duration = 0.1;
animateLayer0.fillMode = kCAFillModeForwards;
animateLayer0.removedOnCompletion = false;
animateLayer1.duration = 0.1;
animateLayer1.fillMode = kCAFillModeForwards;
animateLayer1.removedOnCompletion = false;
animateLayer2.duration = 0.1;
animateLayer2.fillMode = kCAFillModeForwards;
animateLayer2.removedOnCompletion = false;
Here is the accelerometer function's code:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{
self.difference = 0 - acceleration.y;
if (fabsf(self.difference) > 0.01) {
for (int i = 0; i < self.accLayerPoints0.count; i++) {
NSValue *val = [self.accLayerPoints0 objectAtIndex:i];
CGPoint origin = [val CGPointValue];
float x = origin.x + (acceleration.y * ACC_LAYER0_THRESHOLD);
[animateLayer0 setToValue:[NSValue valueWithCGPoint:CGPointMake(x, origin.y)]];
UIImageView *layer0 = [self.accLayerObjects0 objectAtIndex:i];
[layer0.layer addAnimation:animateLayer0 forKey:nil];
}
for (int i = 0; i < self.accLayerPoints1.count; i++) {
NSValue *val = [self.accLayerPoints1 objectAtIndex:i];
CGPoint origin = [val CGPointValue];
float x = origin.x + (acceleration.y * ACC_LAYER1_THRESHOLD);
[animateLayer1 setToValue:[NSValue valueWithCGPoint:CGPointMake(x, origin.y)]];
UIImageView *layer0 = [self.accLayerObjects1 objectAtIndex:i];
[layer0.layer addAnimation:animateLayer1 forKey:nil];
}
for (int i = 0; i < self.accLayerPoints2.count; i++) {
NSValue *val = [self.accLayerPoints2 objectAtIndex:i];
CGPoint origin = [val CGPointValue];
float x = origin.x + (acceleration.y * ACC_LAYER2_THRESHOLD);
[animateLayer2 setToValue:[NSValue valueWithCGPoint:CGPointMake(x, origin.y)]];
UIImageView *layer0 = [self.accLayerObjects2 objectAtIndex:i];
[layer0.layer addAnimation:animateLayer2 forKey:nil];
}
}
}
My problem is after a while the ipad starts having performance issues, starts lagging.
I've used the Allocations tool to verify that it is the code in the accelerometer function that's causing the issue.
Is there a way to release the objects thats not in use anymore or clean the code?
I'm using ARC so I'm not sure how the cleaning works.

I don't think allocations are your problem. I'll bet if you use the time profiler, you'll see you're blocking the main thread with all that accelerometer activity. You are doing all this in a number of very tight loops for every single accelerometer event, which is probably WAY more than you realize.
Consider putting the position computations in a background queue and updating the positions of the UI objects on the main thread (this part is required, since it's generally not safe to update UI from a background queue). Do this by dispatching the actual UI update commands to the main queue. Maybe create a funnel point (-updateUIWithDictionaryContainingComputedPositionsForEachLayer:, called on the main queue with your background-computed values).
It's doubtful also that you need every single accelerometer event. Throttle the processing (Grand Central Dispatch can help there too, or a simple countdown-restart-if-new-event NSTimer) so not every single event is processed. You'll need to experiment to find the perceptual 'sweet spot' as a balance between smooth-looking animation and efficient responsiveness, but I'll bet the number of updates needed for a smooth visual effect is MUCH LOWER than the number of accelerometer events you're processing.

Related

skshapenode adding two nodes?

I'm not sure whether there's an issue with the implementation or the way I'm using it.
However, it's presenting over 2,400 nodes when it should be ~ 1,250
-(void)drawWeb {
//get distance of 50 across
int distanceMargin = _background.frame.size.width/50;
NSLog(#"%i", distanceMargin);
__block int xCounter = distanceMargin;
__block int yCounter = 0;
NSArray *alphabet = [[NSArray alloc] initWithObjects:#"A",#"B",#"C",#"D",#"E",#"F",#"G",#"H",#"I",#"J",#"K",#"L",#"M",#"N",#"O",#"P",#"Q",#"R",#"S",#"T",#"U",#"V",#"W",#"X",#"Y",#"Z", nil];
for (int i = 0; i < 25; i++) {
for (int j = 0; j < 50; j++) {
webPoint *shape = [webPoint shapeNodeWithCircleOfRadius:1];
shape.position = CGPointMake(xCounter, _background.frame.size.height - yCounter);
shape.fillColor = [UIColor blackColor];
shape.alpha = 1;
shape.webPoint = [NSString stringWithFormat:#"%#%i", alphabet[i], j];
shape.positionX = xCounter;
shape.positionY = yCounter;
shape.name = #"webPoint";
[_background addChild:shape];
xCounter = xCounter + distanceMargin;
}
xCounter = distanceMargin;
yCounter = yCounter + distanceMargin;
}
}
By default when creating SKShapeNode, strokeColor is already set and it requires 1 node + 1 draw call. In your example you are setting fillColor too, which requires additional node and additional draw pass.
SKShapeNode is not performant solution in many cases (it should be used sparingly), and in your case, if you enable debugging labels you will probably see that there are a lot of draw calls required for scene rendering. Debugging labels can be enabled in view controller (showsDrawCount = YES)
Draw calls are directly affecting on performance in SpriteKit applications and you should try to keep that number as low as possible. One way would be using SKSpriteNode and texture atlases.

Sticking nodes to SkSpriteNode after enlargement

Problem
As of right now, I've just realised a pretty major design flaw in my application..
So, the problem is:
A rifle shot is fired, and lands based on no trajectory as of right now, I'm toying with the idea. However, the bullets land and their marks are left as nodes.
-(void)applyShot:(int) posX with:(int) posY {
SKSpriteNode *impact = [[SKSpriteNode alloc] initWithColor:[UIColor grayColor] size:CGSizeMake(2, 2)];
impact.zPosition = 1;
impact.userInteractionEnabled = NO;
impact.position = CGPointMake(posX , posY);
[backgroundImage addChild:impact];
}
And posX / posY are sent this way.
//Find difference between centre and background moved distance.
CGPoint positionNow = CGPointMake(backgroundImage.position.x, backgroundImage.position.y);
CGPoint positionPrev = CGPointMake(0.5, 0.5);
float xdiff = positionNow.x - positionPrev.x;
float ydiff = positionNow.y - positionPrev.y;
//Calculate ydrop
int newYDrop = yDrop * 10;
//
CGPoint newPositionOne = CGPointMake(0.5 - xdiff, 0.5 - ydiff);
newPositionOne = CGPointMake((newPositionOne.x + [self myRandom:15 withFieldLower:-15]), (newPositionOne.y - newYDrop));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(secondsDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
[self applyShot:newPositionOne.x with:newPositionOne.y];
});
Which seems to work fine, until I started toying with zoom.
Now, this application basically is a 1080p image for the reticle, and the background enlarges to give the (zoom) effect, then the background is touch-dragged/inverted to give the reticle moving effect.
Then I managed to get it to fire exactly where the crosshair was, which was good.. Then I started toying with zoom and noticed this.
So if it's not too easy to notice, the (bullet hits) are the grey marks, however, when you enlarge the backgroundImage they aren't tied to it. so they remain the same spread.
Solution
Now what I need is to either,
A: tie the nodes to the backgroundImage
B: enlarge the spread to the same factor as the enlargement.
But I'm pretty confused at how to achieve this.
Additional problem
When calibrating the drop, the drop will remain a burden no matter of the zoom as of right now. so if it's a 1 mil-dot drop on 6x zoom, it's also a 1 mil-dot drop at 10x zoom or 1x zoom ( I keep saying zoom when I mean enlargement )
How can I achieve a calibration for drop intensity no matter what enlargement the background image is at?
Thanks for reading and confusing yourself with my dire problems, it's appreciated!
What I've Tried
saving the children to a mutable array, then recreating them after a zoom change;
//recreate children
[backgroundImage removeAllChildren];
for (int i=0; i < [shotsHitX count]; i++) {
double posX = ([shotsHitX[i] doubleValue] / 5) * rifleZoom;
double posY = ([shotsHitY[i] doubleValue] / 5) * rifleZoom;
SKSpriteNode *impact = [[SKSpriteNode alloc] initWithColor:[UIColor grayColor] size:CGSizeMake(2, 2)];
impact.zPosition = 1;
impact.userInteractionEnabled = NO;
impact.position = CGPointMake(posX , posY);
[shotsHitX addObject:[NSNumber numberWithDouble:posX]];
[shotsHitY addObject:[NSNumber numberWithDouble:posY]];
[backgroundImage addChild:impact];
}
//end recreate children
Crash with memory error.
not so good at this!
[backgroundImage removeAllChildren];
for (int i=0; i < [shotsHitX count]; i++) {
double posX = ([shotsHitX[i] doubleValue] / 5) * rifleZoom;
double posY = ([shotsHitY[i] doubleValue] / 5) * rifleZoom;
SKSpriteNode *impact = [[SKSpriteNode alloc] initWithColor:[UIColor grayColor] size:CGSizeMake(2, 2)];
impact.zPosition = 1;
impact.userInteractionEnabled = NO;
impact.position = CGPointMake(posX , posY);
[backgroundImage addChild:impact];
}
//end recreate children
Works, however, now It doesn't just seem quite right..
I think the problem is when the initial zoom goes in it works, then when it reverts it's mixing zoom shots in the array with old shots.. Here we go again, MORE ARRAYS.
//recreate children
[backgroundImage removeAllChildren];
for (int i=0; i < [shotsHitRem count]; i+= 2) {
double posX = ([shotsHitRem[i] doubleValue] / 5) * rifleZoom;
double posY = ([shotsHitRem[i+1] doubleValue] / 5) * rifleZoom;
SKSpriteNode *impact = [[SKSpriteNode alloc] initWithColor:[UIColor grayColor] size:CGSizeMake(2, 2)];
impact.zPosition = 1;
impact.userInteractionEnabled = NO;
impact.position = CGPointMake(posX , posY);
[backgroundImage addChild:impact];
//add olds m4
if ([shotsHitM4 count] > 0) {
posX = ([shotsHitM4[i] doubleValue] / 2) * rifleZoom;
posY = ([shotsHitM4[i+1] doubleValue] / 2) * rifleZoom;
impact.position = CGPointMake(posX, posY);
[backgroundImage addChild:impact];
}
}
//end recreate children
Now I crash attempting to add a sknode which already has a parent
Confusing as it should removeAllChildren before looping
Well after some major messing around
[backgroundImage setScale:rifleZoom];
Programming is my favourite, oh yeah.. Five hours, Oh yeah.. For one line, oh yeah!
I wasn't scaling before, I was creating a new cgsize. and that was my problem.
I now have the issue of scaling and trying to render a new centrepoint as it still remembers the centrepoint of scale = 1 and scale = 2, nightmare.

iOS: jerkiness when animation stops and keeping animation in sync with modal

I am trying to perform a transform for the views on y axis using CABasicAnimation:
for(int x = 0; x < viewsArray.count; x++)
{
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
[startAnimation setToValue:[NSNumber numberWithFloat:DEGREES_RADIANS(-55.0f)]];
[startAnimation setDuration:5.0];
startAnimation.delegate = self;
UIView *view = [viewsArray objectAtIndex:x];
[startAnimation setValue:#"rotate" forKey:#"id"];
[view.layer addAnimation:startAnimation forKey:#"rotate"];
}
Nothing fancy. I would like just to keep the modal and the animation in sync, so in
animationDidStop I try to set the transform again using a for-loop:
if([[anim valueForKey:#"id"] isEqualToString:#"rotate"])
{
aTransform = CATransform3DRotate(aTransform, DEGREES_RADIANS(-55.0), 0.0, 1.0, 0.0);
for(int x = 0; x < viewsArray.count; x++)
{
UIView *view = [viewsArray objectAtIndex:x];
view.layer.transform = aTransform;
}
}
But I realized after the animation stops the animation would jerk to the angle set by the transform in animationDidStop.
Does any one know why and what would be the best way to do this without having to use removedOnCompletion = NO;? I would like to avoid using that and would like to keep the animation always in sync with the modal layer.
You can set it at the same time as you add your animation, but you'll need to supply a fromValue to the animation to stop it updating the presentation layer immediately:
for(int x = 0; x < viewsArray.count; x++)
{
UIView *view = [viewsArray objectAtIndex:x];
NSString *keyPath = #"transform.rotation.y";
NSNumber *toValue = [NSNumber numberWithFloat:DEGREES_RADIANS(-55.0f)];
CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:keyPath];
[startAnimation setFromValue:[view.layer valueForKeyPath:keyPath]];
//[startAnimation setToValue:toValue]; We don't need to set this as we're updating the current value on the layer instead.
[startAnimation setDuration:5.0];
[view.layer addAnimation:startAnimation forKey:#"rotate"];
[view.layer setValue:toValue forKeyPath:keyPath]; // Update the modal
}

Custom CAGradientLayer doesn't animate

I've made a custom class of CAGradientLayer (added radial gradient option).
I works but I can't figure out why I can't animate the colors.
Here's my code.
The animation is a simple CABasicAnimation where I set the toValue to a new array with colors.
#import "ExtCAGradientLayer.h"
NSString * const kExt_CAGradientLayerRadial = #"RadialGradient";
#implementation ExtCAGradientLayer
#synthesize gradientDrawingOptions;
+(BOOL)needsDisplayForKey:(NSString *)key
{
return [key isEqualToString:#"type"] || [super needsDisplayForKey:key];
}
- (void)drawInContext:(CGContextRef)theContext
{
if ([self.type isEqualToString:kExt_CAGradientLayerRadial]) {
size_t num_locations = self.locations.count;
int numbOfComponents = 0;
CGColorSpaceRef colorSpace = NULL;
if (self.colors.count) {
CGColorRef colorRef = (__bridge CGColorRef)[self.colors objectAtIndex:0];
numbOfComponents = CGColorGetNumberOfComponents(colorRef);
colorSpace = CGColorGetColorSpace(colorRef);
}
float *locations = calloc(num_locations, sizeof(float));
float *components = calloc(num_locations, numbOfComponents * sizeof(float));
for (int x = 0; x < num_locations; x++) {
locations[x] = [[self.locations objectAtIndex:x] floatValue];
const CGFloat *comps = CGColorGetComponents((__bridge CGColorRef)[self.colors objectAtIndex:x]);
for (int y = 0; y < numbOfComponents; y++) {
int shift = numbOfComponents * x;
components[shift + y] = comps[y];
}
}
CGPoint position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = floorf(MIN(self.bounds.size.width, self.bounds.size.height) / 2);
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGContextDrawRadialGradient(theContext, gradient, position, 0, position, radius, self.gradientDrawingOptions);
free(locations);
free(components);
}
Layers have a bunch of implicit animations tied to them. This can make them a little funny when interacting with CABasicAnimation. My first recommendation is to avoid CABasicAnimation when working with layers. There's almost never a good reason for it, since layers know how to animate themselves. It's much easier just to do this:
[CATransaction begin];
[CATransaction setAnimationDuration:3];
layer.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor redColor].CGColor,
[UIColor greenColor].CGColor,
nil];
[CATransaction commit];
That's a 3-second animation of the colors. Easy. The begin/commit aren't technically needed in most cases, but they limit the scope of the setAnimationDuration: in case you're animating other things in this transaction.
But maybe you like CABasicAnimation, or you're using it something that returns them (I do that sometimes). You can still combine them with CALayer, but you have to tell CALayer to stop its automatic animation (setDisableActions:). And you still want to use the layer setters rather than toValue:.
[CATransaction begin];
[CATransaction setDisableActions:YES];
CABasicAnimation *anim = [[CABasicAnimation alloc] init];
anim.duration = 3;
[layer addAnimation:anim forKey:#"colors"];
layer.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor redColor].CGColor,
[UIColor greenColor].CGColor,
nil];
[CATransaction commit];
If you use toValue: rather than the setter, it will work, but will "jump back" to the original value at the end.
Avoid solutions that suggest setting fillMode and removedOnCompletion. These generate many subtle problems if you don't understand exactly what they're doing. They seldom are what you really mean; they just happen to work as long as things are very simple.

Synchronising image, text, and positioning with CoreAnimation

I am a bit of a beginner with animations and have been experimenting with CoreAnimation for a couple of days. Feel free to warn me if this question does not make sense, but I'm trying to achieve the following. I have three objects:
one should be an image, moving according to a given pattern
one should be an UIImage that swaps two images
one should be a text (CATextLayer?) whose content changes
The three actions should happen in sync.
As an example, think about a programme showing a sinusoid function, like in a ECG, oscillating between -1 and +1: the first image would then move according to the current value (-1, -0.9, -0.8, ... 0, +0.1, +0.2, ... 1), the swap image would show "+" for positive values and "-" for negative values, and the text would alternate between "Positive" and "Negative".
I've tried with CAAnimationGroup but I'm clearly missing something. Some code:
{
// image that moves
CALayer *img1 = [CALayer layer];
img1.bounds = CGRectMake(0, 0, 20, 20);
img1.position = CGPointMake(x1,y1);
UIImage *img1Image = [UIImage imageNamed:#"image.png"];
img1.contents = (id)img1Image.CGImage;
// image that changes
CALayer *swap = [CALayer layer];
swap.bounds = CGRectMake(0, 0, 30, 30);
swap.position = CGPointMake(x2,y2);
NSString* nameswap = #"img_swap_1.png"
UIImage *swapImg = [UIImage imageNamed:nameswap];
// text
CATextLayer *text = [CATextLayer layer];
text.bounds = CGRectMake(0, 0, 100, 100);
text.position = CGPointMake(x3,y3);
text.string = #"Text";
// create animations
CGFloat duration = 0.2;
CGFloat totalDuration = 0.0;
CGFloat start = 0;
NSMutableArray* animarray = [[NSMutableArray alloc] init];
NSMutableArray* swapanimarray = [[NSMutableArray alloc] init];
NSMutableArray* textanimarray = [[NSMutableArray alloc] init];
float prev_x = 0;
float prev_y = 0;
// I get my values for moving the object
for (NSDictionary* event in self.events) {
float actual_x = [[event valueForKey:#"x"] floatValue];
float actual_y = [[event valueForKey:#"y"] floatValue];
// image move animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
CGPoint startPt = CGPointMake(prev_x,prev_y);
CGPoint endPt = CGPointMake(actual_x, actual_y);
anim.duration = duration;
anim.fromValue = [NSValue valueWithCGPoint:startPt];
anim.toValue = [NSValue valueWithCGPoint:endPt];
anim.beginTime = start;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animarray addObject:anim];
// image swap animation
CABasicAnimation *swapanim = [CABasicAnimation animationWithKeyPath:#"contents"];
swapanim.duration = duration;
swapanim.beginTime = start;
NSString* swapnamefrom = [NSString stringWithFormat:#"%#.png", prev_name];
NSString* swapnameto = [NSString stringWithFormat:#"%#.png", current_name];
UIImage *swapFromImage = [UIImage imageNamed:swapnamefrom];
UIImage *swapToImage = [UIImage imageNamed:swapnameto];
swapanim.fromValue = (id)(swapFromImage.CGImage);
swapanim.toValue = (id)(swapToImage.CGImage);
swapanim.fillMode = kCAFillModeForwards;
swapanim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[swapanimarray addObject:swapanim];
//text animation
CABasicAnimation *textanim = [CABasicAnimation animationWithKeyPath:#"contents"];
textanim.duration = duration;
textanim.fromValue = #"Hey";
textanim.toValue = #"Hello";
textanim.beginTime = start;
[textanimarray addObject:textanim];
// final time settings
prev_x = actual_x;
prev_y = actual_y;
start = start + duration;
totalDuration = start + duration;
}
}
CAAnimationGroup* group = [CAAnimationGroup animation];
[group setDuration:totalDuration];
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
[group setAnimations:animarray];
CAAnimationGroup* swapgroup = [CAAnimationGroup animation];
[swapgroup setDuration:totalDuration];
swapgroup.removedOnCompletion = NO;
swapgroup.fillMode = kCAFillModeForwards;
[swapgroup setAnimations:swapanimarray];
CAAnimationGroup* textgroup = [CAAnimationGroup animation];
[textgroup setDuration:totalDuration];
textgroup.removedOnCompletion = NO;
textgroup.fillMode = kCAFillModeForwards;
[textgroup setAnimations:textanimarray];
[ball addAnimation:group forKey:#"position"];
[swap addAnimation:flaggroup forKey:#"position"];
[text addAnimation:textgroup forKey:#"contents"];
[self.layer addSublayer:ball];
[self.layer addSublayer:swap];
[self.layer addSublayer:text];
}
Now... the problems:
1) the swap image reverts to the original at every swap. So, if I swap A->B, I see it going from A to B in the expected duration time, but then reverting to A. I've read a number of threads on SO about this but couldn't get it to work.
2) changing the string of the text layer in a timed fashion... is this possible with this infrastructure? Basically, I'm trying to get the text and the swap image to change as soon as the first image moves, as described in the example.
3) setting the delegate for the CABasicAnimation doesn't have any effect, although it does for the CAAnimationGroup: as a result, you can't manage events like animationDidStop for every single animation, just for the whole group. Is there any alternative way to do so?
4) following from 3), is it possible, using CAAnimationGroup, to intercept the events to create a stop/start behaviour? Let's suppose I wanted to have play/stop buttons, and to resume the animation from exactly where I had left it?
As a conclusive question, I would simply like to know if anyone did something similar and, most importantly, if this way of doing things (using a CAAnimationGroup) is actually the way to go or if it's better to use CAKeyFrameAnimation or something else.
I managed to solve the problem, albeit with some workarounds.
1) The trick here is that
removedOnCompletion = NO;
fillMode = kCAFillModeForwards;
need to be assigned to every single animation, not to the CAAnimationGroup.
2) This requires a separate animation to be determined for the CATextLayer, and animating the "string" property. In this case, the code was right except for the "contents" key-value which should have been
[text addAnimation:textgroup forKey:#"contentAnimate"];
Alternatively, see point 3. The callback way of operation allows to change a label.text.
3) The only way to do this is to actually not use the CAAnimationGroup and set up a sequence of CABasicAnimation. The general schema is: create the first CABasicAnimation in a function, assign the delegate. In the animationDidStop method, add a callback to the function that creates the CABasicAnimation. This way, each animation will be created. At the same time, this allows to intercept and react to specific events within the animation.
-(void)performNextAnimation:(int)index {
// ... this gives for granted you have an object for #index ...
NSDictionary* thisEvent = [self.events objectAtIndex:index];
float prev_x = [thisEvent valueForKey:#"prev_x"];
float prev_y = [thisEvent valueForKey:#"prev_x"];
float actual_x = [thisEvent valueForKey:#"prev_x"];
float actual_y = [thisEvent valueForKey:#"prev_x"];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
GPoint startPt = CGPointMake(prev_x,prev_y);
CGPoint endPt = CGPointMake(actual_x, actual_y);
anim.duration = 0.5;
anim.fromValue = [NSValue valueWithCGPoint:startPt];
anim.toValue = [NSValue valueWithCGPoint:endPt];
anim.beginTime = start;
[anim setDelegate:self];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[object addAnimation:anim forKey:#"position"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
// ... your conditions go here ...
// ... your actions go here ...
// e.g. set label.text
[self performNextAnimation];
}
4) Following from 3, this is not possible in CAAnimationGroup. Reading the docs, I would say that CAAnimationGroup is not intended to be used for sequences in which each event represents a step forward in time (for this you need to use CAKeyFrameAnimation or a sequence of CABasicAnimation with a callback function, as I did). CAAnimationGroup is intended for linked animation that should be executed on an all-or-nothing basis (similarly to CATransaction).

Resources