We have this animation for some bubble effect on a button , but it prevent him from being clickable. if we disable this animation he works.
Can we fix it to let it work with the animation ?
CGRect initi=[[initialPositions objectAtIndex:b.tag] CGRectValue];
int r1=arc4random()%4;
int r2=arc4random()%4;
initi.origin.x+=r1;
initi.origin.y+=r2;
[UIView animateWithDuration:0.8 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^
{
b.frame=initi;
}
completion:^(BOOL completed){ }];
As far as I know, the button is not actually disabled or unclickable during the animation. When the animation starts the button's touch area is immediately moved to the position where the button will be when the animation ends. This makes it feel like the button were unclickable during the animation.
From touch logic's point of view, the button is just moved from A to B and animation between A and B is just eye candy. This explains why touching doesn't work between the points.
The only working solution to this is to roll your own animation routine. If you want to take this route, this post called The Holy Grail of iOS Animation Intervals? by Ben Bojko should point you to the right direction.
As Markus says in his answer (voted) the button actually moves to it's end position at the beginning of the animation, and will accept clicks there as soon as the animation starts. The animation actually takes place in view hierarchy's "presentation layer".
You can use Markus' suggestion of doing your own animation, but that is processor-intensive and not as smooth as layer-based animation (which is how UIView animation works under the covers.)
If you want to use UIView animation and have your button respond to clicks while the animation is in-flight then you need to make the superview that bounds the entire animation clickable, and implement hit testing on the button's presentation layer to figure out if the tap hits the button or not. (the superview takes the tap, then checks it's coordinates to see if it is inside the buttons' presentation layer using the hitTest: method.)
I have a project on github called iOS CAAnimationGroup demo (link) that shows how to do this, both for CAAnimation based animation and UIView-based animation.
Related
I'm using a modal transition to shift from one XIB to another, and I've got it all working except for one thing: at the moment that the transition begins, all the movement animations I've done on the previous view are reverted.
Here's the method I'm working with:
- (IBAction)chooseInsight:(id)sender {
[CATransaction setCompletionBlock:^{
ContainerViewController *insight = [[ContainerViewController alloc] init];
insight.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:insight animated:YES completion:nil];
}];
[self animateExit];
}
The animateExit method animates a 1-second frame movement for several UI objects, giving the effect that everything flies off the screen leaving a solid-colored background. My hope was that this solid color background would then dissolve into the next view, the ContainerViewController.
But what happens is that the UI objects fly off the screen, we see the solid color background, and then suddenly all the buttons and labels snap back so they can dissolve into the ContainerViewController.
Why is that happening? Has an image of the previous view been cached, to aid the animation? If so, can I refresh the cache before the transition? Or if not, what can I do to make this dissolve work smoothly?
Edit: In case it's relevant, I got the CATransaction bit from this answer about how to delay until the end of an animation. There's a voice in the back of my mind saying that maybe the two animations are the source of the problem, but I'm not familiar enough with iOS animations to figure out how...
all the movement animations I've done on the previous view are reverted.
Because you performed those animations by changing the frames (or centers) of those subviews. But you are also using Autolayout. You can't do that. Frames and Autolayout are enemies to one another.
When the transition comes along, layout happens. That means that the constraints are obeyed - that is what Autolayout means. But you did not change the constraints (which is what you should have done); you changed the frames. The constraints win, so everything goes back to where it was, because that is what the constraints say to do.
Update
The animation is working for setEnabled=NO.
The animation for setEnabled=YES is being triggered when UIScrollView is scrolling, the UIButton is inside the scrollview and the animation for setEnabled=NO is being triggered when UIScrollView is done scrolling.
So, I think the reason why animation for setEnabled=YES is not working is because the view is moving. I am not sure but this seems to be the only logical explanation from what I have found so far. I did a test with dispatch_after() and the animation worked for setEnabled too, in other words the animation is working if it is being triggered when the view is not moving.
What I need to do ?
I have two different background images for UIButton one for UIControlStateNormal and another for UIControlStateDisabled.
I want a effect where UIButton slowly transitions over from one state to another
What have I been doing ?
BOOL enableDisable = YES;
[UIView transitionWithView:((UIButton*)object)
duration:3.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{ [((UIButton*)object) setEnabled:enableDisable]; }
completion:nil];
The Problem
UIButton transforms to setEnabled=NO state over the duration but no matter what I put in the options setEnabled happens almost instantly.
is there something I am missing ?
Thanks in advance for your time and response.
Unfortunately, enable or disabled state for UIView aren't part of animatable properties in apple docs. The animatable properties are:
frame, center, bounds, transform, alpha, backgroundColor, contentStretch
Reference here: UIView animation
However if you want to create custom property for animation, you can have a look at this post which describes a way to achieve it. Create a custom animatable property
I can confirm that your code works as expected both for the transition
enabled: NO -> YES
and for the transition
enabled: YES -> NO
So, my guess is that something else is happening in your app that somehow interferes with the transition. Try defining a completion block like:
...
completion:^(BOOL finished) {
NSLog(#"FINISHED? %#", finished?#"YES":#"NO");
} ];
and add a trace log before the transitionWith call or inside the animation block to check how long the transition runs from start to completion.
My guess is that while the button is transitioning something else is happening that changes its state and as a secondary effect breaks the transition. I fear that without seeing more code, it will not be possible to help you further...
In my app I call this method to move a UIButton
- (void) buttonMovement{
[UIView animateWithDuration:0.8
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
// do whatever animation you want, e.g.,
play_bt.center = CGPointMake(play_bt.center.x, play_bt.center.y-20);
}
completion:NULL];
}
In this way my button done an "up&down" movement but if I "push" it, IBAction doesn't work. If I don't do this animation it work fine. Why?
If this animation is a problem to call the action, what's the way to do an animation that don't give me problems?
Another way is to move the button as an imageview and over I put an invisible button, but I lost the press effetcts, and it became all noise.
A lot of people are providing wrong information here. I tried to weigh in with comments. Now it's time to post a conclusive answer. To quote my answer to the "clicking an animating image/button" question linked by #Radu above:
The short answer is that you can't tap views while they are animating. The reason is that the views don't actually travel from the start to the end location. Instead, the system uses a "presentation layer" in Core Animation to create the appearance of your view object moving/rotating/scaling/whatever.
What you have to do is attach a tap gesture recognizer to a containing view that completely encloses the animation (maybe the entire screen) and then write code that looks at the coordinates of the tap, does coordinate conversion, and decides if the tap is on a view that you care about. If the tap is on a button and you want the button to highlight you'll need to handle that logic too.
I have a sample project on github that shows how to do this when you are doing both UIView animation and Core Animation with CABasicAnimation (which animates layers, not views.)
Core Animation demo with hit testing
A workaround is to implement touchesBegan instead of IBAction for your animating button.
You may want to check the answers given here:Clicking an animating image / button
- (void)touchesBegan:(NSSet*) touches withEvent:(UIEvent *) event{
// Get location of touched point
CGPoint point = [[touches anyObject] locationInView:self.view];
//
// Check if point is inside your button frame
// or compare it with the presentation layer
//
}
UI animations are great, very easy to use, and are used allot. The only one problem I have with it is that while the animation is moving, the view in not receiving any user interaction.
For example, if a have a UIButton that animates every time it's shown, but the user will not be able to click on it until the animation is over.
//This is a UIButton:
- (void)animationApear
{
CGRect frameSelf = self.frame;
frameSelf.origin.y -= frameSelf.size.height;
[UIView animateWithDuration:1.0 delay:0
usingSpringWithDamping:0.8 initialSpringVelocity:0
options:0
animations:^{
[self setFrame:frameSelf];
} completion:nil];
}
Is there any way to deal with this issue?
Thanks!
You need to supply the option UIViewAnimationOptionAllowUserInteraction.
Also, depending on your view architecture, if the button is within the animated view, or a subview thereof, then the actual location of the button isn't moving. Only the presentation layer of the button is moving, so that is why the button may not be receiving taps. A good test is to tap where the button was when the animation started (and make sure the option UIViewAnimationOptionAllowUserInteraction is on) to see if it is still receiving taps.
On solution, when you actually need to animate buttons, is to make repeated short transforms (CGAffineTransforms, for example) and have those movements in aggregate, create the visual effect of the animation. Though in this case the button itself will move, rather than simply it's presentation.
In iOS (version 5 is fine), I have a custom animation to flip and zoom an image transitioning from one UIViewController to another using:
TCFlipZoomSubviewSegue segue=[[TCFlipZoomSubviewSegue alloc] initWithIdentifier:#"SegueToMenuTable" source:self.iViewController destination:self.mViewController];
[segue perform];
and in the perform method I have:
[UIView animateWithDuration:3.75f
delay:0.0f
options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
self.subviewTransformView.layer.transform=transform;
} completion:^(BOOL finished) {
[destination.parentViewController.view bringSubviewToFront:destination.view];
}
It all works fine, but all sorts of problems occur (layout is messed up) if I rotate the device during the animation. What I want is for the animation to complete before the orientation event occurs - which is the standard behaviour in iPhones - eg try rotating the MAIL app immediately after clicking INBOX - it completes the slide transition then rotates. Same with the Stocks app etc. And that's what happens if I use a standard Push animation etc. But I have my own animation to do.
So how do they do that?
How do I prevent (ie delay, or block) the orientation change until my animation finishes? I can easily stop the change by saying NO in shouldAutorotateToInterfaceOrientation: when I'm animating but then i need it to do the orientation after. I could call attemptRotationToDeviceOrientation but surely there must be a better way - The segue itself should be able to complete without being interrupted.
I think the way you mentioned is probably the way you have to do it -- that is, return NO to shouldAutorotateToInterfaceOrientation:, and then call attemptRotationToDeviceOrientation when your animation is done. You would probably need some sort of flag that is set if the user tries to rotate while the animation is in progress, so you would know whether you should call attemptRotationToDeviceOrientation when the animation finishes.