Animation For image in sequence - iphone

I've a screen where following kind of animation has to too be implemented.
The circles unfurl out of the bloom and then spread out to become the individual buttons. Slightly slower the first time it animates (1 sec), slight faster for the second time (.75s), and then faster for all subsequent times (.5s).
But I could not figure out what kind of animation has to be implemented. Any help would be appreciated.

What you describe is a complicated animation. What you should probably do is just break it down into its constituent parts:
Create circular buttons, e.g:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor ...];
button.frame = CGRectMake(...); // probably define this to be square, just off screen to the bottom
button.layer.cornerRadius = button.frame.size.height / 2.0;
button.layer.borderColor = [UIColor ...].CGColor; // white or black as appropriate
button.layer.borderWidth = 2.0;
[self.view insertSubview:button atIndex:0];
[buttons addObject:button];
Animate their appearance on the screen by animating each of the buttons from off screen to the appropriate vertical position using animateWithDuration. You could move them all together, or you might stagger them so they appear to fall into place, using a rendition that includes delay so you can delay each button a little more. Perhaps something like:
for (NSInteger idx = 0; idx < count; idx++) {
[UIView animateWithDuration:duration * (count - idx) / count
delay:duration * idx / count
options:0
animations:^{
UIButton *button = buttons[idx];
button.center = CGPointMake(button.center.x, idx * 50.0 + 100.0);
}
completion:^(BOOL finished) {
//do next step
}];
}
Unfurl the buttons by animating the frame of the button to be the full width that that row will eventually occupy. Again, you can just use animateWithDuration.
Animate the unrounding of the corners so they're square, like your final buttons. This is the one animation that you cannot easily do with animateWithDuration, but rather you might use CAKeyFrameAnimation:
[buttons enumerateObjectsUsingBlock:^(UIView *button, NSUInteger idx, BOOL *stop) {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"cornerRadius"];
animation.values = #[#(button.layer.cornerRadius), #(0.0)];
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
if (idx == 0)
animation.delegate = self;
[button.layer addAnimation:animation forKey:nil];
}];
Your delegate's animationDidStop can initiate the next step of the animation.
Replace the wide button with one that is broken up into the right number of individual buttons for that row. You might want to make these be lined up edge to edge.
Do a final frame adjustment of the buttons so they end up with a little space in between them. Again, you can use animateWithDuration.
Replace the previously blank labels on the buttons with the final text strings. Note, the label on a button is not generally an animatable property, so if you'd like it to fade in nicely, you should use transitionWithView, specifying the common superview for all of those buttons and use an options value of UIViewAnimationOptionTransitionCrossDissolve.
If you look at each one of these steps, you can see that none is terribly complicated. Just break your complicated animation into separate steps, write one method for each animation, and have each trigger the next step of the animation.

UIButtons don't have a rounded rectangle look to them any more in iOS 7. Are you going to create a custom subclass of UIButton?
If so, I would suggest using CAAnimation. You should be able to attach a CALayer to each button and set it's borderColor, backgroundColor, borderWidth, and cornerRadius. When you want to animate the layer, just change the bounds of the layer and it will animate to the larger size.
To change the animation's duration or timing you could enclose your changes in a CATransaction. Something like this:
[CATransaction begin];
[CATransaction setAnimationDuration: 1.0];
CAMediaTimingFunction *linearTiming =
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
[CATransaction setAnimationTimingFunction: linearTiming]
myButton1Layer.bounds = newButton1ounds;
myButton2Layer.bounds = newButton2ounds;
[CATransaction commit];

Related

xcode: Continuously generating images that move in a direction

I've made a program that generates an image at a random x-coordinate at the top of the screen. The image then falls down to the bottom. However, I want to keep generating new images (of the same image) every few seconds so that it's as though these duplicates of the same image are continually "raining" from the top. (Note: Eventually, as I continue to develop this app, I will need to recall the location of each image at any moment, so I believe I will need each spawned image to be part of an array. I also believe I must move each image step-by-step, so I cannot rely on animation).
The problem is: How can I make all of this code repeat every 0.5 seconds so that each newly spawned image has its own moveObject timer. It will be like raindrops falling from the top.
#implementation ViewController {
UIImageView *_myImage;
}
- (void)viewDidLoad
{
srand(time(NULL));e
int random_x_coordinate = rand() % 286;
CGRect myImageRect = CGRectMake(random_x_coordinate, 0.0f, 40.0f, 40.0f);
UIImageView *myImage = [[UIImageView alloc] initWithFrame:myImageRect];
[myImage setImage:[UIImage imageNamed:#"flake.png"]];
myImage.opaque = YES;
[self.view addSubview:myImage];
_myImage = myImage;
//FALLING BIRDS TIMER
moveObjectTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:#selector(moveObject) userInfo:nil repeats:YES];
}
//FALLING BIRDS MOVER
-(void) moveObject { // + means down and the number next to it is how many pixels y moves down per each tick of the TIMER above
_myImage.center = CGPointMake(_myImage.center.x, _myImage.center.y +1);
}
You have different solutions:
Just 1 timer in which you update an array of UIImageView (I prefer this one)
Subclass Imageview and put the timer inside the class so each one would have its own timer
Maybe you can use [UIView animateWithDuration... instead of timers, but not sure if you can use many at the same time.
The problem is: How can I make all of this code repeat every 0.5
seconds so that each newly spawned image has its own moveObject timer.
Look into Core Animation's particle effects -- they're not just for smoke, fog, and fire. By setting up a particle emitter and creating particle cells using your image, you can have Core Animation take care of the whole operation. I don't see much official documentation on the topic, but you can read the reference page for CAEmitterLayer to get an idea of how it works, and then take a look at the tutorial on raywenderlich.com.
Use this function for each raindrop object.Just provide point to move to:
-(void)raindropAnimation:(CGPoint)dropPoint
{
raindrop.frame = CGRectMake(dropPoint.x,0,raindrop.frame.size.width,raindrop.frame.size.height) //here y is 0 ie topmost
[UIView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationCurveEaseInOut
animations:^ {
raindrop.frame = CGRectMake(dropPoint.x,dropPoint.y,raindrop.frame.size.width,raindrop.frame.size.height) //will move to point
}
completion:^(BOOL finished) {
[self perfromSelector:#selector(raindropAnimation:CGPointMake(xnewpointhere,ynewpointhere))];
}];

transition from A image to B image with animation

I have an UIImageView of an white color arrow image. I am animating it to rotate 90 degree's. I also want that UIImageView to change to a black color arrow image as it rotates.
The only solution I can think of is to stack the two images on top of each other, animate both when ever it rotates and change the white color arrow image's alpha (so it fades in and out).
Is there a simpler way to obtain this effect?
Is there a build in transition effect to fade one image out and another image in?
You seem to have a good grasp of what to do. Probably the easiest repeatable way to do this is to create a UIView subclass and add both of your arrows to it. Then add a couple of functions like these.
-(void)rotate90{
blackArrow.alpha = 0;
[self bringSubviewToFront:blackArrow];
[UIView animateWithDuration:1 animations:^{
blackArrow.alpha = 1;
blackArrow.transform = CGAffineTransformMakeRotation(M_PI_2);
whiteArrow.transform = CGAffineTransformMakeRotation(M_PI_2);
}];
}
-(void)rotate0{
whiteArrow.alpha = 0;
[self bringSubviewToFront:whiteArrow];
[UIView animateWithDuration:1 animations:^{
whiteArrow.alpha = 1;
blackArrow.transform = CGAffineTransformMakeRotation(0);
whiteArrow.transform = CGAffineTransformMakeRotation(0);
}];
}

CAAnimation - User Input Disabled

I am trying to animate a button. When I do so (using button.layer addAnimation) the button becomes disabled. Is there any way to allow user interaction when animating? I also tried wrapping everything in a block using animateWithDuration passing in the option UIViewAnimationOptionAllowUserInteraction, but it still doesn't work.
EDIT: It's odd. If I click in the upper corner (where I placed my button) it fires off the event. It's almost like the frame of the button does not follow the animation.
EDIT: What I ended up doing is create an event that fires every 0.1 seconds that sets the button.frame equal to the [[button.layer presentationLayer] frame]. That seemed to do the trick.
Use the following UIView method with UIViewAnimationOptionAllowUserInteraction in the UIViewAnimationOptions parameter:
+(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
the best solution is to change the position of the layer after you call the animation:
[button.layer addAnimation:animation forKey:#"animation"];
button.layer.position = endPosition;
What I ended up doing is create an event that fires every 0.1 seconds that sets the button.frame equal to the button.layer
To me the event firing seemed a bit hacky.
However your mentioning of the [[button.layer presentationLayer] frame] brought me onto the right track :-)
So by attaching a gesture recognizer to the superview of the moving view I can do the following detection:
CGPoint tapLocation = [gestureRecognizer locationInView:self.view];
for (UIView* childView in self.view.subviews)
{
CGRect frame = [[childView.layer presentationLayer] frame];
if (CGRectContainsPoint(frame, tapLocation))
{
....
}
}
I wanted the button to zoom in. So I scale down the layer before I start the animation:
layer.transform = CATransform3DScale (layer.transform, 0.01, 0.01, 0.01);
// some animation code
After the animation I scaled it back.
// some animation code
CATransform3D endingScale = CATransform3DScale (layer.transform, 100, 100, 100);
// some animation code
[layer addAnimation:animation forKey:#"transform"];
layer.transform = endingScale;
Looks like if you directly assign it to the layer the frame will change. However using an animation won't change the frame.

Is it possible to successful animate a moving UIButton?

I'm trying to animate a button which moves around the screen. At any point, the user can push the button. But the button doesn't respond to touches. I've tried an animation block but the button simply moves immediately to its end coordinates, while the frame shows the animation (the button is called bubble):
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:5.0];
CGAffineTransform newTransform = CGAffineTransformMakeScale(3, 3);
bubble.transform = CGAffineTransformTranslate(newTransform, 0, -460);
[UIView commitAnimations];
So then I tried Core Animation with the help of some sample code (the path isn't important, it's just an example):
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CAKeyframeAnimation *theAnimation=[CAKeyframeAnimation animationWithKeyPath:#"position"];
theAnimation.path=thePath;
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.animations=[NSArray arrayWithObject:theAnimation];
theGroup.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theGroup.duration=15.0;
CFRelease(thePath);
[bubble.layer addAnimation:theGroup forKey:#"animatePosition"];
But the buttons still don't respond to touches. Btw, I have several of these 'bubble' buttons on screen at once so having several NSTimers concurrently active wouldn't be optimal.
Can anyone suggest another approach? Should I perhaps animate UIImageViews and make them repsond to touches? Or will I have the same problem? This has been puzzling me for a couple of days so any help much appreciated.
Thanks :)
Michael
When animating a GUI element like the UIButton, the actual position only changes when the animation is done. This means any touch events during the animation will only work at the starting point of the button. You can get the current displayed position of the button by accessing it's presentationLayer. You'll probably need to implement your own event handling, look at the position when the user touches the screen, and compare that to the bounds you get from the presentationLayer.
Well, for anyone interested, here's my solution which works perfectly well:
- (void)startMinigame {
makeBubbles = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(generateRandomBubble) userInfo:nil repeats:YES];
floatBubbles = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:#selector(floatBubbles) userInfo:nil repeats:YES];
}
- (void)generateRandomBubble {
//Get a random image for the bubble
int randomIndex = arc4random()%kNumberBubbles;
UIImage *bubbleImage = [bubbleImages objectAtIndex:randomIndex];
//Create a new bubble object and retrieve the UIButton
Bubble *bubble = [[Bubble alloc]initWithImage:bubbleImage andIndex:randomIndex];
UIButton *bubbleButton = bubble.bubbleButton;
//Configure the button
[bubbleButton addTarget:self action:#selector(bubbleBurst:) forControlEvents:UIControlEventTouchDown];
//Add the bubble to the scene
[self.view insertSubview:bubbleButton belowSubview:bathTub];
//Add the bubble to an array of currently active bubbles:
[self.bubbles addObject:bubble];
[bubble release];
}
- (void)floatBubbles {
//Move every active bubble's frame according to its speed.
for (int i = 0; i < [bubbles count]; ++i) {
Bubble *bubble = [bubbles objectAtIndex:i];
int bubbleSpeed = bubble.speed;
CGRect oldFrame = bubble.bubbleButton.frame;
CGRect newFrame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y - bubbleSpeed, oldFrame.size.width+0.1, oldFrame.size.height+0.1);
bubble.bubbleButton.frame = newFrame;
if (bubble.bubbleButton.frame.origin.y < -100.0) {
[bubbles removeObject:bubble];
[bubble.bubbleButton removeFromSuperview];
}
}
}
This sounds a bit too complicated for UIKit and CoreAnimation. It seems like you're developing something like a game. Why not use a global animation timer for let's say ~60 fps and do your drawing in Quartz2D? Then for each touch you could quickly check any hits between Quartz refreshes.
Try animating the uiview's frame property.

After Animation, View position resets

I am trying to make a view slide from top to bottom. This is not a big deal, I used CABasicAnimation for this. The problem is when I want to remove the view. I use this animation.
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:#"position"];
[animation setDelegate:self];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.layer.position.x, 0 - self.view.bounds.size.height / 2)];
animation.fromValue = [NSValue valueWithCGPoint:self.view.layer.position];
animation.autoreverses = NO;
animation.repeatCount = 0;
animation.duration = 0.25;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
[self.view.layer addAnimation:animation forKey:#"moveX"];
Which animates the view perfectly. But, after the animation finishes, my view appears again. So I added this line :
[self.view removeFromSuperview];
Which removes the view, but with no animation. So I decided to add the remove code to this delegate:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag
So now, the animation works, the view disappears, but sometimes, I can see the view appear and disappear faster, is like after the animation, the view appears, then the animationDidStop delegate is called, and the view disappears, obviously this is awful. What am I doing wrong?
Might want to set these properties. They cause the presentation to be preserved at the end of the animation.
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Then the "animationDidStop:" method can be used to remove the view at the end of the animation:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag {
if (animation == [containerView.layer animationForKey:#"moveX"]) {
// remove view here, add another view and/or start another transition
}
}
Well, according to the Apple sample "MoveMe", this (removedOnCompletion) should work, however, it doesn't seem to.
So, add these lines after your code:
[self.view.layer addAnimation:animation forKey:#"moveX"];
self.view.layer.position = [animation.toValue CGPointValue];
This ensures that after the animation runs, the layer is properly positioned.
I had this issue when performing several animations in an animation group. I had to set a couple properties on the animation group itself, not the individual animations.
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
// MAKE SURE YOU HAVE THESE TWO LINES.
animGroup.removedOnCompletion = NO;
animGroup.fillMode = kCAFillModeForwards;
animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, nil];
animGroup.duration = tAnimationDuration;
[tImageView.layer addAnimation:animGroup forKey:nil];
This one bit me too. You want to set the animation's removedOnCompletion flag to NO. It defaults to YES, which means after the animation is complete, it's removed, and the view reverts to its initial state.
Setting the view to hidden as Rob suggests should do it.
For properties of properties I would stick with the ObjC 2.0 style like you already have in your code.
set.view.hidden = YES;
Can you set the view's hidden property to YES?
I think it would be:
self.view.hidden = YES;
But it might be:
[self.view setHidden:YES];
I turns out I am pretty lame at figuring out the proper way to access properties of properties.