Is it possible to successful animate a moving UIButton? - iphone

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.

Related

Animation For image in sequence

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];

How to know the current position of a UIImageView moving?

I have to do a little game where 2 images are moving horizontally on the same line. I want to know when they are superposed, in order to display a new image instead of 2.
What is the best way to do it ?
Way I was thinking to do :
I need to know the position somewhat during the animation of my UIImageViews and I'll try with timer to refresh the imageView when I discover that the 2 imageViews are close.
I have this function so far to move an Image :
- (void)moveImage:(UIImageView *)image duration:(NSTimeInterval)duration
curve:(int)curve x:(CGFloat)x y:(CGFloat)y
{
// Setup the animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
image.transform = transform;
// Commit the changes
[UIView commitAnimations];
}
Thank you for you help !
I think checking the position multiple times every second is not the best way to do it. If you can, you should precalculate the point in time, when the view overlap!
In any way, you shouldn't use the affinetransform for moving the imageview but just moving the frame of the imageview, by changing the frame or the center property of the view!
If you cant precalculate this, here are some tips on how to implement the timer and checking:
For implementing the timer you can use:
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(checkPosition:) userInfo:nil repeats:YES];
This will call the function every 0.1 seconds.
- (void)checkPosition:(NSTimer *)timer
In this method you could access the imageViews UILayer to find out the actual position of this view by calling
CGFrame currentFrame = [imageView.layer presentationLayer].frame;
To be able to do this you have to import the QuartzCore framework!

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))];
}];

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.

iPhone UIView Animation Disables UIButton Subview

So I've got a problem with buttons and animations. Basically, I'm animating a view using the UIView animations while also trying to listen for taps on the button inside the view. The view is just as large as the button, and the view is actually a subclass of UIImageView with an image below the button. The view is a subview of a container view placed in Interface Builder with user interaction enabled and clipping enabled. All the animation and button handling is done in this UIImageView subclass, while the startFloating message is sent from a separate class as needed.
If I do no animation, the buttonTapped: message gets sent correctly, but during the animation it does not get sent. I've also tried implementing the touchesEnded method, and the same behavior occurs.
UIImageView subclass init (I have the button filled with a color so I can see the frame gets set properly, which it does):
- (id)initWithImage:(UIImage *)image {
self = [super initWithImage:image];
if (self != nil) {
// ...stuffs
UIButton *tapBtn = [UIButton buttonWithType:UIButtonTypeCustom];
tapBtn.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[tapBtn addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
tapBtn.backgroundColor = [UIColor cyanColor];
[self addSubview:tapBtn];
self.userInteractionEnabled = YES;
}
return self;
}
Animation method that starts the animation (if I don't call this the button works correctly):
- (void)startFloating {
[UIView beginAnimations:#"floating" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:10.0f];
self.frame = CGRectMake(self.frame.origin.x, -self.frame.size.height, self.frame.size.width, self.frame.size.height);
[UIView commitAnimations];
}
So, to be clear:
Using the UIView animation effectively disables the button.
Disabling the animation causes the button to work.
The button is correctly sized and positioned on screen, and moves along with the view correctly.
This resolves the issue:
[UIView animateWithDuration:20 delay: 0.0 options: UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
...
The animation is just eye candy. The animation lags behind the actual movement of the view. The button is already at the destination point when the animation starts. You just see a movie of the view/button moving.
If you want a button to be clickable during the animation, you'll have to make the animation yourself.
... was experiencing this same problem because my code was doing one large animation per block. I made an NSTimer based solution, like the one suggested above, and it worked... yet the movement was jerky (unless I inserted animation within every timer event trigger).
So, since animation was required anyway, I found a solution which requires no timer. It animates only a short distance and thus the button click is still accurate, with only a small error which is my case is very unnoticeable in the UI, and can be reduced depending on your params.
Note below that the error at any given time is < 15.0, which can be reduced for more accuracy depending on your animation speed requirements. You can also reduce the duration time for more speed.
- (void)conveyComplete:(UIView*)v
{
[self convey:v delay:0];
}
- (void)convey:(UIView*)v delay:(int)nDelay
{
[UIView animateWithDuration:.5
delay:nDelay
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations: ^
{
CGRect rPos = v.frame;
rPos.origin.x -= 15.0;
v.frame = rPos;
}
completion: ^(BOOL finished)
{
[self conveyComplete:v];
}];
}