In my app I want to move a little UIImageView with inside a .png; this is a little insect and I want to simulate his flight. At example I want that this png do when it move an inverted eight as the infinite simbol ∞
You may use CoreAnimation. You can subclass a view, create a subview for the insect, and then assign an animation to it, following a defined path.
Your UIImageView could be animated. If it's a fly, you can do a few frames for wing moves:
NSArray *images = [NSArray arrayWithObjects:..., nil];
insect.animationImages = images;
insect.animationDuration = ??;
insect.animationRepeatCount = 0;
[insect startAnimating];
Then set an init frame for the insect:
insect.frame = CGRectMake(-120, 310, [[images objectAtIndex:0] size].width, [[images objectAtIndex:0] size].height);
And then define the path:
CGMutablePathRef aPath;
CGFloat arcTop = insect.center.y - 50;
aPath = CGPathCreateMutable();
CGPathMoveToPoint(aPath, NULL, insect.center.x, insect.center.y);
CGPathAddCurveToPoint(aPath, NULL, insect.center.x, arcTop, 240, -100, 490, 360);
CAKeyframeAnimation* arcAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
arcAnimation.repeatCount = HUGE_VALF;
[arcAnimation setDuration: 4.5];
[arcAnimation setAutoreverses: NO];
arcAnimation.removedOnCompletion = NO;
arcAnimation.fillMode = kCAFillModeBoth;
[arcAnimation setPath: aPath];
CFRelease(aPath);
[insect.layer addAnimation: arcAnimation forKey: #"position"];
I leave how to do the infinite loop path up to you :)
Hope it helps!
Normally, if you were to be moving things around, I'd suggest using [UIView animate...]. However, you want something to move on a complex, curvy path. So instead, I'd suggest coming up with an equation that gives the (x,y) for the insect as a function of time, and then start an NSTimer with a fairly small time interval, and every time you get an update, move the insect (perhaps using [UIView animate...]).
Another way to go is to use a 2-d animation framework such as cocos2d - then, you can get an 'update' call linked to the frame refresh rate, inside of which you update the position of your insect using the same equation as from above.
Related
I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.
Well here is my problem:
I have two images : flakeImage and ViewToRotate. What I want is that if flakeImage touches ViewToRotate, ViewToRotate.alpha=0.5; but when FlakeImage appears on the screen ViewToRotate.alpha=0.5; without touching it. I think it's a problem with my view beacause I have :
UIImageView* flakeView = [[UIImageView alloc] initWithImage:flakeImage];
here is the code :
UIImageView* flakeView = [[UIImageView alloc] initWithImage:flakeImage];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView beginAnimations:nil context:flakeView];
// set up how fast the flake will fall
[UIView setAnimationDuration:7 ];
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
// set a stop callback so we can cleanup the flake when it reaches the
// end of its animation
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
How can I solve this please ?
if someone could help me it would be very cool.
I have a bit of sophomoric experience with this, having written http://itunes.apple.com/us/app/balls/id372269039?mt=8. If you check that app out, you will see a bit of the same problem. This topic is a pretty deep rabbit hole. WHen I started that app, I didn't even know how to write a decent game loop. You will need that first because you need to do precise time-step calculations. AFA the collisions, you update your model and view separately, so if you update the model and objects overlap, you need to back them up until they don't collide and then update your view with the result. If you plan to have a lot of colliding objects, you may hit a wall using UIViews. To complicate things more, if your objects are round, CGRectIntersectsRect won't exactly work. That complicates the math a bit, but it's not too bad. With my app, I found it quite difficult to get the physics to look realistic. THe problem became that ball A and B overlap, so you back them up, then they now intersect other balls, etc, etc. This link is a good starting point, but there are quite a few examples of code out there that "almost" work.
CGRect has a intersection function. The frames of UIViews are CGRects.
if (CGRectIntersectsRect(view1.frame, view2.frame) == 1)
NSLog(#"The views intersect");
else
NSLog(#"The views do not intersect");
The problem I foresee is that if the rects have lots of whitespace, they will appear to intersect before they actually touch
Secondly, you should switch up to block animations. It's strongly encouraged
UIImageView* flakeView = [[[UIImageView alloc] initWithImage:flakeImage] autorelease];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView animateWithDuration:.7
animations:^ {
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
};
Did this all from memory, no idea if there are errors.
Circular Collision:
a^2 + b^2 < c^2 means they collide
if(pow(view2.frame.origin.x - view1.frame.origin.x, 2) +
pow(view2.frame.origin.y - view1.frame.origin.y, 2) <
pow(view1.frame.size.width/2, 2))
{
//collision
}
else
{
//no collision
}
Again, all from memory, check for errors on your own
This is a weird request, but I'm using Core Animation (CALayers), and I want my animation to be choppy and non-smooth. I want an image I set up to rotate like a second hand on a clock. Here's my code:
UIImage *arrowImage = [UIImage imageNamed:#"arrow.jpg"];
CALayer *arrow = [CALayer layer];
arrow.contents = (id)arrowImage.CGImage;
arrow.bounds = CGRectMake(0, 0, 169.25, 45.25);
arrow.position = CGPointMake(self.view.bounds.size.width / 2, arrowImage.size.height / 2);
arrow.anchorPoint = CGPointMake(0.0, 0.5);
[self.view.layer addSublayer:arrow];
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0];
anim1.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim1.duration = 4.0;
[arrow addAnimation:anim1 forKey:#"transform"];
It produces a gliding motion, which I don't want. How do I get around this?
Any help is appreciated.
If you want it to be really choppy, don't use Core Animation at all. On the other hand, if you want something somewhere in between those two extremes, don't use linear media timing. Instead, you might want to try kCAMediaTimingFunctionEaseIn so that the animation accelerates slightly as the hand moves.
The simple way to do this would be to simply apply a transform to your view. The second hand would snap from one position to the next. Just change the rotation by 360/60 = 6 degrees for each second.
If you want the second-hand to do an animation for each tick, you could use a very fast UIView block-based animation. (say with a 1/15 second duration or so.)
Take a look at the UIView class methods who's names start with animateWithDuration.
Something like this:
- (void) moveSecondHand;
{
seconds++;
angle = M_PI*2*seconds/60 - M_PI/2;
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
[UIView animateWithDuration: 1.0/15
animations: *{
secondHand.transform = transform
}];
}
That's about all it would take. You're trigger that code with a timer once a second. By default animations use ease-in, ease-out timing, which models physical movement pretty well. Try different durations, but 1/15 is probably a good starting point (you want it fast, but not too fast to see.)
If you want a wobble to your animation you will need to get much fancier, and create an animation group that first moves it by the full amount, and then does a repeating animation that overshoots the stopping point by a small amount and then goes back.
i am trying to implement a graph which a uiview draws. There are 3 UIViews to animate a left/right slide. The problem is, that i can't cancel the UIView animation. So I replaced the UIViews by CALayer. Now, the question is if CALayer is appicable for this? Is it normal to draw on a CALayer like this? And why is a CALayer so slow when I manipulate the frame properties.
CGRect frame = curve.frame;
frame.origin.x -= diffX;
[curve setFrame:frame];
Is there a alternativ?
P.S. I am a german guy. Sorry for mistakes.
I got the animation with CATransaction, but now I will animate a x move with CABasicAnimation. That's no problem expect that the position of the layer go back to the previous x.
CABasicAnimation *theAnimation;
theAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
theAnimation.delegate = self;
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
CGPoint position = [[curves objectAtIndex:i]position];
position.x = [[curves objectAtIndex:i]position].x - diffX;
[theAnimation setToValue:[NSValue valueWithCGPoint:position]];
[[curves objectAtIndex:i] addAnimation:theAnimation forKey:[NSString stringWithFormat: #"translate.x.%d", index]];
The position changes the position (e.g. xStart = 100, xEnd = 200), but when the animation ends the layer goes back to the beginning x (e.g. x = 100).
Is that normal? How can I solve this problem, that the end position doesn't change anymore?
I tried to changed the removeOnComplete property but that didn't effect.
Hope for help.
Markus
Not sure what you mean by 'slow', but setting the frame of a CALayer in this way uses 'implicit animation'. That is, it will animated the transition from the old frame to the new frame. You can turn this off:
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
[curve setFrame:frame];
[CATransaction commit];
However, this is usually considered an advantage of CALayer. You way want to just use UIViews here, which will not, by default, animate transitions such as this.
Instead of setting the destination position in theAnimation, just set the position property of the thing you want to move.
When you addAnimation:theAnimation, you're setting the "visual style" of any changes to the keyPath property you specified.
When you change the position of the object that the animation is attached to, say from (0,0) to (500,500), CoreAnimation will animate the change for you. The theAnimation object doesn't need the start and end positions, since the underlying object has them.
I am developing an iPhone game in which birds bounce.
I have set up the images for animating the wings of the flying bird like this:
[imgBird[i] setAnimationImages:birdArrayConstant];
[imgBird[i] setAnimationDuration:1.0];
[imgBird[i] startAnimating];
Now how I make the bird move is to use an NSTimer to fire every 0.03 seconds which adds/subtracts 1 from the x or y coordinate from imgBird[i].center.
I learnt about doing it like this from here. http://icodeblog.com/2008/10/28/iphone-programming-tutorial-animating-a-ball-using-an-nstimer/
But the issue is the birds slow down as soon as another timer (for moving my ship the same way) fires and returns back to original speed as i stop moving the ship.
Is there a better way to keep the bird moving except NSTimer?
The movement of bird is an infinite loop.
You will need to import CoreGraphics and the QuartzCore frameworks into you project.
Add these lines to the top of your file.
#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>
...
UIImageView *bird = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bird2.png"]];
CALayer *b = bird.layer;
// Create a keyframe animation to follow a path to the projected point
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"scale"];
animation.removedOnCompletion = NO;
// Create the path for the bounces
CGMutablePathRef thePath = CGPathCreateMutable();
// Start the path at my current location
CGPathMoveToPoint(thePath, NULL, bird.center.x, bird.center.y);
CGPathAddLineToPoint(thePath, NULL,20, 500.0);
/* // very cool path system.
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
320.0,500.0,
320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
566.0,500.0,
566.0,74.0);
*/
//animation.rotationMode = kCAAnimationRotateAuto;
animation.path = thePath;
animation.speed = 0.011;
//animation.delegate = self;
animation.repeatCount = 1000000;
// Add the animation to the layer
[b addAnimation:animation forKey:#"move"];
Hope that helps a bit.
From your question it seems you are using more than one timer. Use just one to animate all your objects. Somewhere, when your app starts, have this:
[NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(mainLoop:) userInfo:nil repeats:YES];
Then, in main loop, something like this:
- (void)mainLoop:(NSTimer *)timer
{
// compute new position for imgBirds
for (int i=0; i<kBirdCount; i++)
{
imgBirds[i].center = CGPointMake(somex, somey);
}
// compute new position for ship
ship.center = CGPointMake(somex, somey);
}
Also, you should compute somex and somey as a function of your timer interval. In that way, if you change it, your animation will still look the same.
You'll want to use Core Animation instead of manually moving the birds. Take a look at CAAnimation in the docs. You basically set up an animation, the path you want it to move along, and just tell it to run. It'll take care of the rest. It also has support for easing which will make the pace slow down and accelerate so it looks more natural.