Flash screen white at moment of camera capture? - ios

I'd like to flash (and then fade out) the screen right at the moment of camera capture to give the user the indication that a picture has been taken (besides just an auditory clue).
Where would such an animation be placed? Also, how would it be implemented such that I can control the duration of the fadeout?
Note: I've created a custom overlay for my particular camera picker.
Anything indicating that a picture has been taken is what I am looking for.

I'm not sure where you would place the animation because I don't know how exactly you capture the picture (maybe you could post the code), but here's the code for an animation to flash the screen white:
//Header (.h) file
#property (nonatomic, strong) UIView *whiteScreen;
//Implementation (.m) file
#synthesize whiteScreen;
- (void)viewDidLoad {
self.whiteScreen = [[UIView alloc] initWithFrame:self.view.frame];
self.whiteScreen.layer.opacity = 0.0f;
self.whiteScreen.layer.backgroundColor = [[UIColor whiteColor] CGColor];
[self.view addSubview:self.whiteScreen];
}
-(void)flashScreen {
CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:#"opacity"];
NSArray *animationValues = #[ #0.8f, #0.0f ];
NSArray *animationTimes = #[ #0.3f, #1.0f ];
id timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
NSArray *animationTimingFunctions = #[ timingFunction, timingFunction ];
[opacityAnimation setValues:animationValues];
[opacityAnimation setKeyTimes:animationTimes];
[opacityAnimation setTimingFunctions:animationTimingFunctions];
opacityAnimation.fillMode = kCAFillModeForwards;
opacityAnimation.removedOnCompletion = YES;
opacityAnimation.duration = 0.4;
[self.whiteScreen.layer addAnimation:opacityAnimation forKey:#"animation"];
}
You also asked how to control the fadeout duration. You can do this by adjusting the values in the animationTimes array. If you're not familiar with how CAKeyframeAnimations work, then here's quick briefer. The total duration of the animation is controlled by the opacityAnimation.duration = 0.4. This makes the animation 0.4 seconds long. Now onto what animationTimes does. Each value in the array is a number between 0.0 and 1.0 and corresponds to an element in the 'animationValues' array. The value in the times array defines the duration of the corresponding keyframe value as a fraction of the total duration of the animation. For example, in the above animation, the times array contains the values 0.3 and 1.0, which correspond to the values 0.8 and 0.0. The total duration is 0.4, so that means that the whiteScreen view which has its opacity initially at 0.0, takes
0.4 * 0.3 = 0.12 seconds.
to raise the opacity to 0.8. The second value, 0.0, makes the layer go transparent again. This takes up the remainder of the time (0.4 - 0.12 = 0.28 seconds).

Related

Make CABasicAnimation for iOS Running Multiple Times

I'm building an app that will have an animation of one image "walking" across the screen, using simple CABasicAnimation. I have it set for it to walk a certain distance in a duration, and then stop until the user gives it more commands, in which it will continue walking for the same distance and duration again. The issue that I have is that after the first time, the image will stop where it should, but it won't continue to walk, it will jump back to its original spot and start over. I thought I had this set correctly on the origin point, but I guess not.
CABasicAnimation *hover = [CABasicAnimation animationWithKeyPath:#"position"];
hover.fillMode = kCAFillModeForwards;
hover.removedOnCompletion = NO;
hover.additive = YES; // fromValue and toValue will be relative instead of absolute values
hover.fromValue = [NSValue valueWithCGPoint:CGPointZero];
hover.toValue = [NSValue valueWithCGPoint:CGPointMake(110.0, -50.0)]; // y increases downwards on iOS
hover.autoreverses = FALSE; // Animate back to normal afterwards
hover.duration = 10.0; // The duration for one part of the animation (0.2 up and 0.2 down)
hover.repeatCount = 0; // The number of times the animation should repeat
[theDude.layer addAnimation:hover forKey:#"myHoverAnimation"];
Your from value is set to be zero and it's not being updated.
hover.fromValue = [NSValue valueWithCGPoint:CGPointZero];
You have to update this value with your to value each time.
Here I've put your code in a function where you can update starting and ending points.
- (void)moveFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint {
CABasicAnimation *hover = [CABasicAnimation animationWithKeyPath:#"position"];
hover.fillMode = kCAFillModeForwards;
hover.removedOnCompletion = NO;
hover.additive = YES; // fromValue and toValue will be relative instead of absolute values
hover.fromValue = [NSValue valueWithCGPoint:fromPoint];
hover.toValue = [NSValue valueWithCGPoint:toPoint]; // y increases downwards on iOS
hover.autoreverses = FALSE; // Animate back to normal afterwards
hover.duration = 10.0; // The duration for one part of the animation (0.2 up and 0.2 down)
hover.repeatCount = 0; // The number of times the animation should repeat
[theDude.layer addAnimation:hover forKey:#"myHoverAnimation"];
}
You can move the guy further by calling this function with new points each time.
[self moveFromPoint:CGPointZero toPoint:CGPointMake(110.0, -50.0)]
[self moveFromPoint:CGPointMake(110.0, -50.0) toPoint:CGPointMake(160.0, -50.0)]
EDIT:
I see that you want to move the guy with the same ratio but in different lenght each time.
Add this variable just after the #interface:
#property (nonatomic) CGPoint oldPointOfTheGuy;
And add this new function just after the previous function:
- (void)moveByDistance:(CGFloat)distance {
CGPoint newPointOfTheGuy = CGPointMake(self.oldPointOfTheGuy.x + 2.2*distance, self.oldPointOfTheGuy.y + distance);
[self moveFromPoint:self.oldPointOfTheGuy toPoint:newPointOfTheGuy];
self.oldPointOfTheGuy = newPointOfTheGuy;
}
And set a starting point for the guy in your viewDidLoad:
self.oldPointOfTheGuy = CGPointMake(110.0, -50)
Now we have set the old position of the guy as where we know he spawns for the first time.
From now on, each time we want to move him, we will call this:
[self moveByDistance:20];
What this function does is, since that it already knows your x / y ratio which is 2.2, it just adds 20 to your old y position and adds 2.2 * 20 to your old x position. And each time a new position is set, old position is updated.
Hope this helps.

Is there an issue with this use of kCAMediaTimingFunctionEaseIn?

I'm using CALayer, CAReplicatorLayer and CABasicAnimation to animate a bar chart into place. The bar chart is made of up "segments" which I render as small rectangles with a CALayer. Then I add them as a sublayers to CAReplicatorLayer in a loop. Finally, I add an animation for each small rectangle that drops the rectangle into its place in the chart.
Here is the final state of the animation:
And here is what it looks like in the middle of the animation:
Here's the code that makes it happen:
#define BLOCK_HEIGHT 6.0f
#define BLOCK_WIDTH 8.0f
#interface TCViewController ()
#property (nonatomic, strong) NSMutableArray * blockLayers; // blocks that fall from the sky
#property (nonatomic, strong) NSMutableArray * replicatorLayers; // replicator layers that will replicate the blocks into position
#end
#implementation TCViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.blockLayers = [NSMutableArray array];
self.replicatorLayers = [NSMutableArray array];
NSArray * dataBlocksData = #[#1,#2,#3,#1,#5,#2,#11,#14,#5,#12,#10,#3,#7,#6,#3,#4,#1];
__block CGFloat currentXPosition = 0.0f;
[dataBlocksData enumerateObjectsUsingBlock:^(NSNumber * obj, NSUInteger idx, BOOL *stop) {
currentXPosition += BLOCK_WIDTH + 2.0f;
if (obj.integerValue == 0) return;
// replicator layer for data blocks in a column
CAReplicatorLayer * replicatorLayer = [[CAReplicatorLayer alloc] init];
replicatorLayer.frame = CGRectMake(currentXPosition, 0.0f, BLOCK_WIDTH, 200.0f);
// single data block layer (must be new for each replicator layer. can't reuse existing layer)
CALayer * blockLayer = [[CALayer alloc] init];
blockLayer.frame = CGRectMake(0, 200-BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
blockLayer.backgroundColor = [UIColor whiteColor].CGColor;
[replicatorLayer addSublayer:blockLayer];
replicatorLayer.instanceCount = obj.integerValue;
replicatorLayer.instanceDelay = 0.1f;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, -BLOCK_HEIGHT, 0); // negative height moves back up the column
[self.blockLayers addObject:blockLayer];
[self.replicatorLayers addObject:replicatorLayer];
}];
}
- (void)viewDidLayoutSubviews {
// add replicator layers as sublayers
[self.replicatorLayers enumerateObjectsUsingBlock:^(CAReplicatorLayer * obj, NSUInteger idx, BOOL *stop) {
[self.view.layer addSublayer:obj];
}];
}
- (void)viewDidAppear:(BOOL)animated {
// add animations to each block layer
[self.blockLayers enumerateObjectsUsingBlock:^(CALayer * obj, NSUInteger idx, BOOL *stop) {
// position animation
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
animation.fromValue = #(-300);
animation.toValue = #(0);
animation.duration = 1.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
[obj addAnimation:animation forKey:#"falling-block-animation"];
}];
}
Finally, here's the catch. If you specify no timingFunction, a Linear function or an EaseIn function the animations don't fully complete. Like the blocks don't fall completely into place. It looks like they're close to finishing but not quite. Just a frame or two off. Swap out the timing function assignment in -viewDidAppear: and you'll get this weirdness:
Go ahead, try it out. I even tried specifying a function with control points (link) that are exactly like an EaseIn function:
// use control points for an EaseIn timing:
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.00 :1.0 :1.0];
// or simply specify the ease in timing function:
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
For some reason EaseOut and Default timing functions work fine but an ease in style function appears to take longer to complete, visually, perhaps because of the length of the curve of interpolation?
I have tried many things to narrow down the issue:
Not looping over these structures and using only 1 layer and 1 replicator layer with the same animation. By that I mean, animate just one column. No luck. Looping isn't the issue.
Put the creation, sublayer-adding and animation-adding code all in -viewDidAppear. Didn't seem to make a difference if the animations were added earlier or later in the view's life cycle.
Using properties for my structures. No difference.
Not using properties for my structures. Again, no difference.
Added callbacks for animation completion to remove the animation so as to leave the columns in the correct "final" state. This was not good but worth a try.
The issue is present in both iOS 6 and 7.
I really want to use an EaseIn timing function because its the best looking animation for something falling into place. I also want to use the CAReplicatorLayer because it does exactly what I want to do.
So what's wrong with the EaseIn timing functions as I'm using them? Is this a bug?
I agree that this appears to be a bug in the combination of CAReplicatorLayer and the kCAMediaTimingFunctionEaseIn timing function.
As a workaround, I suggest using a keyframe animation with three keyframes. The first two keyframes define your desired animation with the ease-in timing. The last keyframe is defined to be identical to the second keyframe, but gives the animation additional time to sit on the final keyframe, which seems to let the replicator layer animate that last replicated layer into the final state.
My suggestion therefore is:
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
// extra keyframe duplicating final state
animation.values = #[#-300.0f, #0.0f, #0.0f];
// double length of original animation, with last half containing no actual animation
animation.keyTimes = #[#0.0, #0.5, #1.0];
animation.duration = 2.0f;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
// ease-in for first half of animation; default after
animation.timingFunctions = #[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[obj addAnimation:animation forKey:#"falling-block-animation"];

Chaining keyframe animations

I'm trying to chain two keyframe-based animations, but the second animation won't play for some reason. Any idea what's going on?
// Create an animation group to contain all album art animations
CAAnimationGroup *albumArtAnimationGroup = [CAAnimationGroup animation];
albumArtAnimationGroup.duration = 3.0;
albumArtAnimationGroup.repeatCount = 0;
// First album art translation animation
CGMutablePathRef albumCoverPath = CGPathCreateMutable();
CGPathMoveToPoint(albumCoverPath, NULL,
albumCover.layer.position.x,
albumCover.layer.position.y);
CGPathAddLineToPoint(albumCoverPath, NULL,
albumCover.layer.position.x-[UIScreen mainScreen].bounds.size.height,
albumCover.layer.position.y);
CAKeyframeAnimation *albumCoverTranslationAnimation;
albumCoverTranslationAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
albumCoverTranslationAnimation.calculationMode = kCAAnimationLinear;
albumCoverTranslationAnimation.path = albumCoverPath;
albumCoverTranslationAnimation.duration = 1.0;
// Second album art translation animation
CGMutablePathRef albumCoverPath1 = CGPathCreateMutable();
CGPathMoveToPoint(albumCoverPath1, NULL,
albumCover.layer.position.x+[UIScreen mainScreen].bounds.size.height,
albumCover.layer.position.y);
CGPathAddLineToPoint(albumCoverPath1, NULL,
albumCover.layer.position.x,
albumCover.layer.position.y);
CAKeyframeAnimation *albumCoverTranslationAnimation1;
albumCoverTranslationAnimation1 = [CAKeyframeAnimation animationWithKeyPath:#"position"];
albumCoverTranslationAnimation1.calculationMode = kCAAnimationLinear;
albumCoverTranslationAnimation1.path = albumCoverPath1;
albumCoverTranslationAnimation1.duration = 1.0;
CFTimeInterval localAlbumLayerTime = [albumCover.layer convertTime:CACurrentMediaTime() fromLayer:nil];
albumCoverTranslationAnimation1.beginTime = localAlbumLayerTime + 1.0;
albumArtAnimationGroup.animations = #[albumCoverTranslationAnimation, albumCoverTranslationAnimation1];
[albumCover.layer addAnimation:albumArtAnimationGroup forKey:#"position"];
Edit
Solved. It turns out that either Apple's documentation was misleading, or I was using CACurrentMediaTime incorrectly. The code below did the trick.
albumArtAnimationGroup.duration = 2.0;
albumCoverTranslationAnimation.duration = 1.0;
albumCoverTranslationAnimation1.beginTime = 1;
albumArtAnimationGroup.animations = #[albumCoverTranslationAnimation, albumCoverTranslationAnimation1];
[albumCover.layer addAnimation:albumArtAnimationGroup forKey:#"position"];
However, according to Apple, I may possibly run into issues regarding the timing since I am not using CACurrentMediaTime(), as shown below.
To assist you in making sure time values are appropriate for a given
layer, the CALayer class defines the convertTime:fromLayer: and
convertTime:toLayer: methods. You can use these methods to convert a
fixed time value to the local time of a layer or to convert time
values from one layer to another. The methods take into account the
media timing properties that might affect the local time of the layer
and return a value that you can use with the other layer. Listing 5-3
shows an example that you should use regularly to get the current
local time for a layer. The CACurrentMediaTime function is a
convenience function that returns the computer’s current clock time,
which the method takes and converts to the layer’s local time.
Listing 5-3 Getting a layer’s current local time
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
When you add multiple animations to an animation group, the beginTime property starts at 0, and ends at the duration of the animation. So to chain a second animation, set it's beginTime to the duration of the first animation, and make the animation group's duration long enough for the entire animation sequence.
BTW, it might be simpler to create a single CAKeyframeAnimation that has the 2 paths combined together into one. It would be a lot less code.

Create smooth animation with UIImageView animation

I'm currently animating between 4 images like this:
UIImageView *tom3BeforeImage;
tom3Images = [NSArray arrayWithObjects: [UIImage imageNamed:#"floortom_before1.png"],[UIImage imageNamed:#"floortom_before2.png"],[UIImage imageNamed:#"floortom_before3.png"],[UIImage imageNamed:#"floortom_before4.png"], nil ];
tom3BeforeImage.animationImages = tom3Images;
tom3BeforeImage.animationDuration = 0.75;
[tom3BeforeImage startAnimating];
It works fine, except that the animation is choppy between the images. I need the duration to be exactly .75 seconds, so speeding it up is not an option.
What's the best way to have the animation be smoother between the images, kind of like blending between each image change?
Thanks!
If you're using frame based UIImageView animation, and the animation must be .75 seconds, then the only way I know of to make it smoother is to create more frames. Try 30 frames/second, or about 22 frames. That should give very smooth motion.
If you want some sort of cross-dissolve between frames then you won't be able to use UIView frame animation. you'll have to use UIView block animation (using animateWithDuration:animations: or its cousins.)
You could create a sequence of cross-dissolves between your frames where the total duration of the sequence is .75 seconds. Have each transition trigger the next transition in it's completion block.
Something like this:
You'll need 2 image views, stacked on top of each other. You'll fade one out and the other in at the same time. You'll need to set the opaque flag to NO on both.
Lets call them tom3BeforeImage1 and tom3BeforeImage2
Add an int instance variable imageCount and make your array of images, tom3Images, and instance variable as well:
- (void) animateImages;
{
CGFloat duration = .75 / ([tom3Images count] -1);
//Start with the current image fully visible in tom3BeforeImage1
tom3BeforeImage1.image = tom3Images[imageCount];
tom3BeforeImage1.alpha = 1.0;
//Get the next image ready, at alpha 0, in tom3BeforeImage2
tom3BeforeImage2.image = tom3Images[imageCount+1];
tom3BeforeImage2.alpha = 0;
imageCount++
[UIView animateWithDuration: duration
delay: 0
options: UIViewAnimationOptionCurveLinear
animations:
^{
//Fade out the current image
tom3BeforeImage1.alpha = 0.0;
//Fade in the new image
tom3BeforeImage2.alpha = 1.0;
}
completion:
^{
//When the current animation step completes, trigger the method again.
if (imageCount < [tom3Images count] -1)
[self animateImages];
}
];
}
Note that I banged out the code above in the forum editor without having had enough coffee. It likely contains syntax errors, and may even have logic problems. This is just to get you thinking about how to do it.
Edit #2:
I'm not sure why, but I decided to flesh this out into a full-blown example project. The code above works passably well after debugging, but since it's fading one image out at the same time it's fading another one in, the background behind both image views shows through.
I reworked it to have logic that only fades the top image in and out. It puts the first frame in the top image view and the second frame in the bottom image view, then fades out the top image view.
The project is up on github, called Animate-Img. (link)
Then it installs the third frame in the top image view and fades it IN,
Then it installs the 4th fame in the bottom image view and fades out the top to expose the bottom, etc, etc.
I ended up creating a generalized method
- (void) animateImagesWithDuration: (CGFloat) totalDuration
reverse: (BOOL) reverse
crossfade: (BOOL) doCrossfade
withCompletionBlock: (void (^)(void)) completionBlock;
It will animate a set of images, into a pair of image views, optionally reversing the animation once it's done. It takes a completion block that gets called once the animation is finished.
The animate button actually calls a method that repeats the whole animation sequence. It's currently set to only run it once, but changing a constant will make the program repeat the whole sequence, if desired.
I do had the requirement to have animation with array of images. Initially when i used animationImages property of imageview, I got the desired animation but the transition between the images were not smooth, I then used CAKeyframeAnimation to achieve the smooth transition, the catch is to use timingFunction along with correct calculationMode. I am not sure this is the exact the answer for the question but this is one way to make the animation smoother,
below is the code for that
For more info on calculation mode please see Apple Documentation
- (void) animate
{
NSMutableArray * imageArray = [[NSMutableArray alloc] init];
int imageCount = 8;
for (int i=0; i<=imageCount; i++) {
[imageArray addObject:(id)[UIImage imageNamed:[NSString stringWithFormat:#“image%d”,i]].CGImage];
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.calculationMode = kCAAnimationLinear;// Make sure this is kCAAnimationLinear or kCAAnimationCubic other mode doesnt consider the timing function
animation.duration = 8.0;
animation.values = imageArray;
animation.repeatCount = 1; // Change it for repetition
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards; // To keep the last frame when animation ends
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[imageView.layer addAnimation:animation forKey:#"animation"];
}
UPDATE- Swift 3
func animate() {
var imageArray = [CGImage]()
let imageCount: Int = 3
for i in 0...imageCount {
imageArray.append((UIImage(named: String(format:"image\(i)"))?.cgImage!)!)
}
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = kCAAnimationLinear
// Make sure this is kCAAnimationLinear or kCAAnimationCubic other mode doesnt consider the timing function
animation.duration = CFTimeInterval(floatLiteral: 8.0)
animation.values = imageArray
animation.repeatCount = 1
// Change it for repetition
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
// To keep the last frame when animation ends
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animImageView.layer.add(animation, forKey: "animation")
}

CAShapeLayer path animation stutters while UIImageView is moving

I've been having a problem that I can't seem to figure out. In my app I use a custom UIImageView class to represent movable objects. Some are loaded with static .png images and use frame animation, while others are loaded with CAShapeLayer paths from .svg files. For some of those, I'm getting stutter when the layer is animating from one path to another, while the UIImageView is moving.
You can see it in the linked video. When I touch the mermaid horn, a note spawns (svg path) and animates into a fish (another svg path), all while drifting upwards. The stuttering occurs during that animation / drift. It's most noticeable the third time I spawn the fish, around 19 seconds into the video. (The jumping at the end of each animation I need to fix separately so don't worry about that)
http://www.youtube.com/watch?v=lnrNWuvqQ4w
This does not occur when I test the app in iOS Simulator - everything is smooth. On my iPad 2 it stutters. When I turn off the note/fish movement (drifting upward), it animates smooth on the iPad, so obviously it has to do with moving the view at the same time. There's something I'm missing but I can't figure it out.
Here is how I set up the animation. PocketSVG is a class I found on Github that converts an .svg to a Bezier path.
animShape = [CAShapeLayer layer];
[animShape setShouldRasterize:YES];
[animShape setRasterizationScale:[[UIScreen mainScreen] scale]];
PocketSVG *tSVG;
UIBezierPath *tBezier;
PocketSVG *tSVG2;
UIBezierPath *tBezier2;
CAShapeLayer *tLayer2;
CABasicAnimation *animBezier;
CABasicAnimation *animScale;
tSVG = [[PocketSVG alloc] initFromSVGFileNamed:[NSString stringWithFormat:#"%#", tName]];
// Use PocketSVG to convert the SVG to a Bezier Path
tBezier = tSVG.bezier;
animShape.path = tBezier.CGPath;
animShape.lineWidth = 1;
animShape.strokeColor = [[UIColor blackColor] CGColor];
animShape.fillColor = [[UIColor blackColor] CGColor];
animShape.fillRule = kCAFillRuleNonZero;
// Set the frame & bounds to position the piece and set anchor point for rotation
// parentPaper is the Mermaid the note spawns from
CGPoint newCenter;
CGFloat imageScale = fabsf(parentPaper.transform.a);
newCenter = // Code excised, set center based on custom spawn points in relation to parent position and scale
animShape.bounds = CGPathGetBoundingBox(animShape.path);
CGRect lBounds = CGRectMake(newCenter.x, newCenter.y, animShape.bounds.size.width, animShape.bounds.size.height);
[animShape setFrame:lBounds];
halfSize = CGPointMake(animShape.bounds.size.width/2, animShape.bounds.size.height/2);
animShape.anchorPoint = CGPointMake(prp.childSpawnX, prp.childSpawnY);
// Create the end path for animation
tSVG2 = [[PocketSVG alloc] initFromSVGFileNamed:[NSString stringWithFormat:#"%#2", tName]];
tBezier2 = tSVG2.bezier;
tLayer2 = [CAShapeLayer layer];
tLayer2.path = tBezier2.CGPath;
// Create the animation and add it to the layer
animBezier = [CABasicAnimation animationWithKeyPath:#"path"];
animBezier.delegate = self;
animBezier.duration = prp.frameDur;
animBezier.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animBezier.fillMode = kCAFillModeForwards;
animBezier.removedOnCompletion = NO;
animBezier.autoreverses = behavior.autoReverse;
if (prp.animID < 0) {
animBezier.repeatCount = FLT_MAX;
}
else {
animBezier.repeatCount = prp.animID;
}
animBezier.fromValue = (id)animShape.path;
animBezier.toValue = (id)tLayer2.path;
[animShape addAnimation:animBezier forKey:#"animatePath"];
// also scale the animation if spawned from a parent
// i.e. small note to normal-sized fish
if (parentPaper != nil) {
animScale = [CABasicAnimation animationWithKeyPath:#"transform"];
animScale.delegate = self;
animScale.duration = prp.frameDur;
animScale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animScale.fillMode = kCAFillModeForwards;
animScale.removedOnCompletion = YES;
animScale.autoreverses = NO;
animScale.repeatCount = 1;
animScale.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(imageScale, imageScale, 0.0)];
animScale.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0.0)];
[animShape addAnimation:animScale forKey:#"animateScale"];
}
[self.layer addSublayer:animShape];
And this is basically how I'm moving the UIImageView. I use a CADisplayLink at 60 frames, figure out the new spot based on existing position of animShape.position, and update like this, where self is the UIImageView:
[self.animShape setPosition:paperCenter];
Everything else runs smooth, it's just this one set of objects that stutter and jump about when running on the iPad itself. Any ideas of what I'm doing wrong? Moving it the wrong way, perhaps? I do get confused with layers, frames and bounds still.
To fix this I ended up changing the way the UIImageView moves. Instead of changing the frame position through my game loop, I switched it to a CAKeyframeAnimation on the position since it's a temporary movement that only runs once. I used CGPathAddCurveToPoint to replicate the sine wave movement that I originally had. It was rather simple and I probably should have done it that way to begin with.
I can only surmise that the stuttering was due to moving it through the CADisplayLink loop while its animation separately.

Resources