I have written some code that I had hoped would randomly spin a block into view on the screen, pause, then spin it out. The action should repeat itself in a semi random fashion. This kinda of works in that it shows the block, spins out, then shows itself again after some time however:
It does not spin in (it only spins out)
It never changes position
What vital thing am I missing from this code?
-(void)randomlyShowBlock{
int randomTime = 1 + arc4random() % (3 - 1);
[RandomBlock setNeedsDisplay];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, randomTime * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
int randomX = 10 + arc4random() % ((int)self.view.frame.size.width - 10);
int randomY = 10 + arc4random() % ((int)self.view.frame.size.height - 10);
RandomBlock.frame = CGRectMake(randomX, randomY, RandomBlock.frame.size.width, RandomBlock.frame.size.height);
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^(void) {
RandomBlock.transform = CGAffineTransformMakeRotation(DegreesToRadians(-90));
RandomBlock.backgroundColor = [[UIColor alloc] initWithRed:50.0/255.0 green:124.0/255.0 blue:203.0/255.0 alpha:1.0];
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4 delay:1 options:UIViewAnimationOptionCurveEaseInOut
animations:^(void) {
RandomBlock.transform = CGAffineTransformMakeRotation(DegreesToRadians(0));
RandomBlock.backgroundColor = [[UIColor alloc] initWithRed:220.0/255.0 green:220.0/255.0 blue:220.0/255.0 alpha:0];
}
completion:^(BOOL finished){
NSLog(#"finished animation");
//model
[self randomlyShowBlock];
}];
}];
});
}
You're not changing the frame in the animation, so it's not surprising that it's not moving.
As for the spinning, my guess is that the transform is not initially an identity transform, but rather a nil one, and iOS doesn't know how to animate from nothing to something. Try setting it, before the animation block, to CGAffineTransformIdentity.
Related
I have checked and tried variety of answers . But still it didn't work for me.
Here is my code,
-(void)animate {
[img1 startAnimating];
[UIView animateWithDuration:10
animations:^{
img1.transform = CGAffineTransformMakeTranslation(0.0, -1000);}];
[img2 startAnimating];
[UIView animateWithDuration:13
animations:^{
img2.transform = CGAffineTransformMakeTranslation(0.0, -1000);}];
}
I want to get animation started when i press one button and stop when i press another. It works fine for start and stop . but problem comes when i restart it by pressing on start button which calls this method again. Nothing happens when i call second time ??????
Transformation unable to perform twice
Hi you need to use the completion block to perform this animation,
Initialize like this some where else....
#interface YourClass()
{
CGAffineTransform trans1 ;
CGAffineTransform trans2 ;
}
#End
....
-(void)animate {
[_img1 startAnimating];
[UIView animateWithDuration:10
animations:^{
trans1 = _img1.transform; //
_img1.transform = CGAffineTransformMakeTranslation(0.0, -1000);} completion:^(BOOL finished) {
_img1.transform = trans1;
}];
[_img2 startAnimating];
[UIView animateWithDuration:13
animations:^{
trans2 = _img2.transform;
_img2.transform = CGAffineTransformMakeTranslation(0.0, -1000);} completion:^(BOOL finished) {
_img2.transform = trans2;
}];
}
I need a view to enter the screen with oscillating animation and finally, the animation should stop in a natural way (decreasing oscillations - pendulum effect). I have added the subview above the screen so that the view rotates into the screen when required. The code for adding the subview is:
myView.layer.anchorPoint = CGPointMake(1.0, 0.0);
[[self view] addSubview:myView];
[myView setHidden:YES];
// Rotate 75 degrees to hide it off screen
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_RADIANS(75));
bannerView.transform = rotationTransform;
bannerView.center = CGPointMake(((self.view.bounds.size.width)/2.0), -5.0);
[self performSelector:#selector(animateSwing) withObject:nil afterDelay:3.0];
The way I'm trying to achieve this that the view should rotate one full semi circle & back rotation, then rotate one semi circle rotation and finally come to halt at desired point using EaseOut animation curve. The code for my animateSwing() method is given below:
- (void)animateSwing {
NSLog(#"ANIMATING");
[myView setHidden:NO];
CGAffineTransform swingTransform = CGAffineTransformIdentity;
swingTransform = CGAffineTransformRotate(swingTransform, DEGREES_RADIANS(-20));
[UIView animateWithDuration:0.30
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[UIView setAnimationRepeatCount:1.5];
[UIView setAnimationRepeatAutoreverses:YES];
myView.transform = swingTransform;
}completion:^(BOOL finished){
[UIView animateWithDuration:0.10
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
myView.transform = CGAffineTransformMakeRotation(DEGREES_RADIANS(0));
}completion:^(BOOL Finished){
}];
}];
}
For some reason the above code isn't working. If I do not chain animations, the code performs the semi-circle routine. But if I chain animations like above, it just oscillates a little bit around the desired point and ends abruptly.
Please suggest a fix to this code OR suggest a way to implement the required animation
Thanks
You want to use a keyframe animation. I actually have an example of a "decreasing waggle" animation in my book (http://www.apeth.com/iOSBook/ch17.html#_keyframe_animation):
CompassLayer* c = (CompassLayer*)self.compass.layer;
NSMutableArray* values = [NSMutableArray array];
[values addObject: #0.0f];
int direction = 1;
for (int i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
[values addObject: #(direction*M_PI/(float)i)];
}
[values addObject: #0.0f];
CAKeyframeAnimation* anim =
[CAKeyframeAnimation animationWithKeyPath:#"transform"];
anim.values = values;
anim.additive = YES;
anim.valueFunction =
[CAValueFunction functionWithName: kCAValueFunctionRotateZ];
[c.arrow addAnimation:anim forKey:nil];
Of course that isn't identical to what you're trying to do, but it should get you started.
In my app I have this code in viewWillAppear. It simply animates random object from top of the screen to the bottom. The problem is I need to detect collision and what I have learnt so far is it is impossible to retrieve the coordinates at any time for an animation. so how can I implement it by using NSTimer? or shall I use NSTimer? I could not figure it out. Any clue will be appreciated.
-(void)viewWillAppear:(BOOL)animated
{
for (int i = 0; i < 100; i++) {
p = arc4random_uniform(320)%4+1;
CGRect startFrame = CGRectMake(p*50, -50, 50, 50);
CGRect endFrame = CGRectMake(p*50, CGRectGetHeight(self.view.bounds) + 50,
50,
50);
animatedView = [[UIView alloc] initWithFrame:startFrame];
animatedView.backgroundColor = [UIColor redColor];
[self.view addSubview:animatedView];
[UIView animateWithDuration:2.f
delay:i * 0.5f
options:UIViewAnimationCurveLinear
animations:^{
animatedView.frame = endFrame;
} completion:^(BOOL finished) {
[animatedView removeFromSuperview];
}];
}
I would ditch NSTimer for CADisplayLink to send a message to your target. NSTimer can cause stuttering animations since it doesn't synchronize with the device's screen refresh rate. CADisplayLink does exactly that and makes your animations run like butter.
Documentation:
http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
Ultimately, using CADisplayLink looks something like this:
- (id) init {
// ...
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(update:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// ...
}
- (void) update {
[myView updatePositionOrSomethingElse];
}
Your statement:
is it is impossible to retrieve the coordinates at any time for an
animation
seems incorrect did you check this?
It looks like you can use the CALayer's presentationLayer property to extract coordinates info during an animation:
CGRect movingFrame = [[yourView.layer presentationLayer] frame];
I will use this info to check time to time if there is a collision during the animation. So I will use the timer to check the collision status not for animating the views.
You can have your objects animate in small increments (in a loop may be). Every time you complete your animation, you can get the coordinates.
I think you could use the beginAnimations - commitAnimations syntax in a for cycle it could count for eg. till 10, so after each cycle you can check for collision
CGRect incrementedViewFrame;
for(int i = 0; i < 10; ++i)
{
incrementedViewFrame = CGRectMake(/*calculate the coords here*/);
if(collision)
{
//do stuff
}
else
{
//do an other animation cycle
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.1f];
[[self viewToAnimate]setFrame:incrementedViewFrame];
[UIView commitAnimations];
}
}
I hope that helps!
I am trying to use following code to perform few animations
-(void) performSlidingfromX:(int) xx fromY:(int) yy
{
UIImageView *Image= [self getImage];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationBeginsFromCurrentState:true];
[UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
[token setFrame:CGRectMake(xx, yy, 64, 64)];
[UIView commitAnimations];
}
and i am calling like it in for loop
for (i = 0; i < totMoves; i++) {
Moment *m = [moments objectAtIndex:i];
int xx= [m X];
int yy= [m Y];
[self performSlidingfromX:xx fromY:yy];
}
The problem that i am facing is that its animating to final position, for example , If i input the following moments for xx,yy
0,0
50,0
50,50
It moves the image from 0,0 to 50,50 diagonally, I want it to slide to horizantly first and then vertical.
Any Help?
Thanks
use new block animations. it is easy and stable:
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[token setFrame:CGRectMake(xx, 0, 64, 64)];
//here you may add any othe actions, but notice, that ALL of them will do in SINGLE step. so, we setting ONLY xx coordinate to move it horizantly first.
}
completion:^(BOOL finished){
//here any actions, thet must be done AFTER 1st animation is finished. If you whant to loop animations, call your function here.
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{[token setFrame:CGRectMake(xx, yy, 64, 64)];} // adding yy coordinate to move it verticaly}
completion:nil];
}];
The problem is u continuously calling "performSlidingfromX:xx fromY:yy" inside for loop.
Try this code:
i=0;
Moment *m = [moments objectAtIndex:i];
int xx= [m X];
int yy= [m Y];
[self performSlidingfromX:xx fromY:yy];
-(void) performSlidingfromX:(int) xx fromY:(int) yy
{
i++;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationBeginsFromCurrentState:true];
[UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
[token setFrame:CGRectMake(xx, yy, 64, 64)];
[UIView commitAnimations];
[self performSelector:#selector(call:) withObject:[NSNumber numberWithInt:i] afterDelay:1.1];
}
-(void)call
{
Moment *m = [moments objectAtIndex:i];
int xx= [m X];
int yy= [m Y];
[self performSlidingfromX:xx fromY:yy];
}
Making an animation is not a blocking call. Your code does not stop and wait for the animation to finish. Immediately after you start the animation, the next iteration of your loop will run. That creates a new animation, which affects the same property, so it replaces the previous animation. The result you get is as if you had only run the last iteration of your loop.
Unfortunately there is no simple block of code to do what you want to do. You need to detect when the animation finishes, and then start the next one. You need to keep track of your state (mostly which i you are on) in something with wider scope than local variables.
I'm having difficulty working with the animation blocks. I'm trying to have the first animation change the alpha of an image so that it flashes, then after 10 seconds disappears. the firstAnimation then moves to secondAnimation which does the same thing only to a different image. the process then repeats itself infinitely.
I've been working w/ this for a while now and cannot make it repeat infinitely. Another problem I had was having this animation process delay - so that it begins after 20 seconds, enough time for an audio file to play.
- (void) firstAnimation {
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
[UIView animateWithDuration:1.0 delay:10.0 options:options animations:^
{
myImage.alpha = 0.0;
myImage.alpha = 1.0;
}
completion:^(BOOL finished){
[self secondAnimation];
}
];
}
thanks as always, for any help. I've searched for example code and tutorials but all I found is basically what I'm doing in the code above.
I think you have to set the initial value of your alpha outside the animation block and only the value you want it to animate to inside the block.
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
myImage.alpha = 0.0;
[UIView animateWithDuration:1.0 delay:10.0 options:options animations:^
{
myImage.alpha = 1.0;
}
animateImageWithCount:(int)aCount{
if( aCount >= [imageArray count]){
NSLog(#"Done all images");
return;
}
UIImage *myImage = [ImageArray objectAtIndex:aCount];
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
myImage.alpha = 0.0;
[UIView animateWithDuration:1.0 delay:10.0 options:options animations:^{
myImage.alpha = 1.0;
}
completion:^(BOOL finished){
[self animateImageWithCount:(aCount+1)];
}];
}