Understanding of UIAnimation - iphone

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.

Related

animateWithDuration not working correctly

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.

Oscillating UIView With Stopping Motion (iOS)

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.

beginAnimation with For Statement not working

-updated code-
-(void)animateDot {
dotMotion.center = (*doodlePoints)[0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationRepeatCount:100];
for(int i = 1; i < doodlePoints->size(); i++){
dotMotion.center = (*doodlePoints)[i];
}
[UIView commitAnimations];
}
I have a vector of points and I want this to do the animation from point to point. I am only getting from the 2nd to the last to the last working. not anything before.
Any ideas?
So i tried doing this differently. Nothing.
My application has to work 3.x so i can't use animation blocks.
Even better try the following:
-(void) nextAnimation
{
CGPoint point = [[self.animatePoints objectAtIndex:0] CGPointValue];
[self.animatePoints removeObjectAtIndex:0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationRepeatCount:100];
dotMotion.center = point;
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
if ([self.animatePoints count] > 0) {
[self nextAnimation];
}
}
-(void)animateDot {
self.animatePoints = [NSMutableArray array];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
for(int i = 1; i < doodlePoints->size(); i++){
[self.animatePoints addObject:[NSValue valueWithCGPoint:(*doodlePoints)[i]]];
}
[self nextAnimation];
}
This is a rough example of loading the individual points in an NSMutableArray property that is incrementally processed and emptied as each animation completes. You could just as easily use an internal index property to the doodlePoints array but I did it this way should you ever need to pass in a different collection of points. In my example we set the animation delegate and callback selector so we can be told about each animation completing. We then schedule the an animation to the next point and remove it from the array.
You need to exit from your for loop, and return from this method, after each line segment for each animation segment to be displayed. The UI is only updated after you return to the UI run loop.
Continue each iteration of the loop in another delayed or delegate callback method. Save the iterrator variable in an instance variable between methods.
You want something similar to this (not tested):
-(void)animateTo:(CGPoint) point {
dotMotion.center = (*doodlePoints)[0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationRepeatCount:100];
dotMotion.center = point;
[UIView commitAnimations];
}
-(void)animateDot {
for(int i = 1; i < doodlePoints->size(); i++){
[self animateTo:(*doodlePoints)[i]];
}
}
Though you probably don't want to schedule these to run concurrently. You'd probably like to chain the animations using the animation completion callback and fire off the next one in sequence after each animation completes. Edited for formatting

CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [0 0; nan nan] crash in view

I've looked a lot into this, but I can only seem to get webView and tables relating to this issue. Mine's totally different it seems with the same crash exception:
CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [0 0; nan nan]
Basically what I have here is a view that fades and scales images. I've recently decided to change my code using CGAffineTransformScale in a UIView animation instead of scaling things up a notch every time a timer ticks. This uses way less processing power.
But no matter what order I have the pictures in, it always crashes after the 19th one. It does not seem to be an issue with the positioning coordinate array that it refers to, because it is only 6 in length and loops over after it reaches its length. So for some reason now since I've implemented this animation code it gives me that crash. Anyone know why?
Here's the part I've changed since it started crashing:
-(void) onTimer{
if(scaleTimer_A==5){
imageView_A.image = [UIImage imageNamed:[attractList_A objectAtIndex:imageIndex_A]];
imageView_A.frame = CGRectMake(300, 200, 3.86, 3.86);
imageView_A.center = CGPointMake(attractLocs_x_A[attractLocs_A_index], attractLocs_y_A[attractLocs_A_index]);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
imageView_A.alpha = 1;
imageView_A.transform = CGAffineTransformScale(imageView_A.transform, 100, 100);
[UIView commitAnimations];
}
if(scaleTimer_A==10){
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.75];
imageView_A.alpha = 0;
imageView_A.transform = CGAffineTransformScale(imageView_A.transform, 1.2, 1.2);
[UIView commitAnimations];
scaleTimer_A=0;
imageIndex_A+=1;
if(imageIndex_A==imageIndex_A_size){
imageIndex_A=0;
}
attractLocs_A_index+=1;
if(attractLocs_A_index==attractLocs_A_SIZE){
NSLog(#"Back to zero A");
attractLocs_A_index=0;
}
NSLog(#"Image A =%#", [attractList_A objectAtIndex:imageIndex_A]);
}
scaleTimer_A+=1;}
EDIT:
Here's how I got the code above to work without the crashing problem using CGAffineTransformIdentity.
-(void) onTimer{
if(scaleTimer_A==5){
imageView_A.image = [UIImage imageNamed:[attractList_A objectAtIndex:imageIndex_A]];
imageView_A.transform = CGAffineTransformIdentity;
imageView_A.center = CGPointMake(attractLocs_x_A[attractLocs_A_index], attractLocs_y_A[attractLocs_A_index]);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
imageView_A.alpha = 1;
imageView_A.transform = CGAffineTransformScale(CGAffineTransformIdentity, 100, 100);
[UIView commitAnimations];
}
if(scaleTimer_A==10){
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.75];
imageView_A.alpha = 0;
imageView_A.transform = CGAffineTransformScale(CGAffineTransformIdentity, 120, 120);
[UIView commitAnimations];
scaleTimer_A=0;
imageIndex_A+=1;
if(imageIndex_A==imageIndex_A_size){
imageIndex_A=0;
}
attractLocs_A_index+=1;
if(attractLocs_A_index==attractLocs_A_SIZE){
NSLog(#"Back to zero A");
attractLocs_A_index=0;
}
NSLog(#"Image A =%#", [attractList_A objectAtIndex:imageIndex_A]);
}
scaleTimer_A+=1;}
According to the stack trace the problem is here
imageView_A.frame = CGRectMake(300, 200, 3.86, 3.86);
Try to set the imageView_A.transform to identity. Another solution (and I think better) would be using the UIScrollView for scaling (it is also could be made animated).
Edit: try this
imageView_A.image = [UIImage imageNamed:[attractList_A objectAtIndex:imageIndex_A]];
imageView_A.frame = CGRectMake(300, 200, 3.86, 3.86);
imageView_A.center = CGPointMake(attractLocs_x_A[attractLocs_A_index], attractLocs_y_A[attractLocs_A_index]);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
imageView_A.alpha = 1;
imageView_A.frame = CGRectMake(300, 200, 386.0f, 386.0f);
[UIView commitAnimations];

UIView Animaton Resets to beginning

I'm attempting to run an animation so that the scrollview's contentoffset gets continually scrolling down.
However, after each repeat, it will animate from the original position and it is not progressing.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationRepeatCount: 100];
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.y += 50;
scrollView.contentOffset = contentOffset;
scrollView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
any ideas ?
Why are you attempting to manually scroll the view? Can't you use the method
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
?
Regardless, I believe the reason your animation isn't working as you expect is because you're setting a repeat count instead of actually re-rendering your animation each time. If you want to work around this problem, you could animate again in the animation callback by creating a method wrapping the animation and passing parameters via the context pointer.
- (void) animateContentOffsetByAmount:(CGFloat)amount numberRemaining:(int)num {
if (num == 0) return;
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:
#selector(scrollAnimationStoppedWithID:finished:context:)];
//NSArray released in callback
NSArray* contextArray = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:amount],
[NSNumber numberwithInt:num], nil]
[UIView beginAnimations:nil context:contextArray];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.y += amount;
scrollView.contentOffset = contentOffset;
scrollView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
- (void) scrollAnimationStoppedWithID:(NSString*)id
finished:(NSNumber*)finished context:(void*)context {
NSArray* contextArray = (NSArray*)context;
CGFloat amount = [(NSNumber*)[contextArray objectAtIndex:0] floatValue];
int count = [(NSNumber*)[contextArray objectAtIndex:1] intValue];
count = count - 1;
[contextArray release];
[self animateContentOffsetByAmount:amount numberRemaining:count]
}
Then in your code, just call
[self animateContentOffsetByAmount:50 numberRemaining:100];