I am trying to animate a transform of a UIButton with CGAffineTransformRotate, and while it is performing the animation properly, it shifts the button about 15 pixels down and to the left before doing it. Here's the code performing the animated transformation:
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionTransitionNone
animations:^{
self.addCloseButton.transform = CGAffineTransformRotate(self.addCloseButton.transform, degreesToRadians(45));
}
completion:nil];
When I reverse the transformation it does the same thing except it shifts it back to its original position before animating (15 pixels up and 15 pixels to the right), and I do that with this code:
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionTransitionNone
animations:^{
self.addCloseButton.transform = CGAffineTransformIdentity;
}
completion:nil];
Why would this shift occur? The button was created using interface builder, and the shift happens immediately even if I set the animation duration higher or add a delay.
I figured it out: turns out having "Use Autolayout" selected on my xib (which adds a bunch of auto constraints) messes things up when trying to use transforms. Turning it off fixed my problem.
It is posible to fix this while still using auto layout and storyboards. See my answer on this question: https://stackoverflow.com/a/19582959
Related
I have four UIViews, positions on the four sides of my full view, e.g.
View 3
View 4 View 2
View 1
(Note that the bottom view is offset a little bit)
They are all 44x44 pixels and look exactly the same.
I have the views move counter clockwise to the next view's location (i.e. View 1 moves to View 2, View 2 moves to View 3, etc.), and View 4 moves to View 1, but not offset. This part works normally, the code is as follows:
[UIView animateWithDuration:0.5 animations:^{
[view1 setFrame:view2.frame];
[view2 setFrame:view3.frame];
[view3 setFrame:view4.frame];
[view4 setFrame:centerFrame];
} completion:^(BOOL finished) {
[self reloadData];
}];
centerFrame is view1's frame not offset.
In the method reloadData, the frames are move back to their original positions, and, because they all look the same, the only thing that users should see is the movement of the bottom view back to its offset position (although it is really changing from view4 to view1). This change looks a little bit sudden, and I wanted to animate it, but I don't want the views to move back, just cross dissolve, so that only the bottom view looks like it changed. I've tried:
[UIView transitionWithView:self.view duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
// Reset frames
} completion:nil];
and
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
// Reset frames
} completion:nil];
but both move the views. So my question,
How do I animate a UIView frame change with a cross dissolve animation instead of moving between the points?
Thanks in advance!
You can animated alpha = 0, update the frames in the completion block and then animated alpha = 1. Make the duration of each animation block 1/2 the usual so together they'll take the same time as the rotation event.
If you want to do a simultaneous dissolve then duplicate all the views, set their alpha = 0, update their frames and then animate the alpha of both groups:
old views'.alpha = 0new views'.alpha = 1
a third solution - group all the views in a subview, copy the whole hierarchy and then animate the transition between them using options:UIViewAnimationOptionTransitionCrossDissolve
I'd like to use a UIKitDynamics UISnapBehaviour to spice up the animation (appearance + position change on rotation) of a button that itself is positioned in a view using Auto Layout.
I understand that while applying UIKitDynamics forces I need to disable auto-layout constraints from the button temporarily. I am thinking of the following process …
Get the target center/bounds of the button before the Auto Layout based transition happens (but after it is triggered). Save that value.
Temporarily disable all Auto Layout/constraints of the button
Apply the UISnapBehaviour. Feed it with the saved target center or bounds value from Auto Layout (from step 1).
When the UIKitDynamics animation is finished re-enable the constraints to prepare for any further layout change
Is this the right approach?
Which delegate/layout should be used for those those respective steps + how do I get the target center of a view from Auto Layout before the actual Auto Layout based animation/transition happens?
I might suggest a different approach that approximates the UISnapBehavior, but avoids trying to marry UIKit Dynamics with an auto layout where you are relying upon the auto layout engine to determine the final position of the view. (Obviously, if you knew the final destination, then you'd just suspend auto layout, snap, and when the snap was done, you'd then apply the constraints.)
For the bouncing effect as the view snaps into place, you can use animateWithDuration with usingSpringWithDamping parameter, e.g.:
// create the view
UIView *view = ...;
view.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:view];
// add all of the constraints for the final position
NSDictionary *views = NSDictionaryOfVariableBindings(view);
[self.view addConstraints:...];
// animate the application of those constraints with low `withSpringWithDamping`
[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.5 initialSpringVelocity:0.0 options:0 animations:^{
[view layoutIfNeeded];
} completion:nil];
If you also want some rotation as it snaps into place, you can use animateKeyframesWithDuration:
[UIView animateKeyframesWithDuration:1.0 delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
view.transform = CGAffineTransformMakeRotation(M_PI_4 / 2);
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
view.transform = CGAffineTransformMakeRotation(-M_PI_4 / 4);
}];
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.125 animations:^{
view.transform = CGAffineTransformMakeRotation(M_PI_4 / 16);
}];
[UIView addKeyframeWithRelativeStartTime:0.875 relativeDuration:0.125 animations:^{
view.transform = CGAffineTransformMakeRotation(0);
}];
} completion:nil];
This isn't precisely the UISnapBehavior, but approximates it pretty closely. You can play around with the timing and amount of rotations in the key frame animation as well as the spring damping factor, too. But this illustrates one approach to get snap-like behavior using block-based animation.
I'm currently testing my apps for the release of IOS 8. I noticed that after I performed an animation block, the animation resets if I update the text of any label. I ran a simple example with one method shown below. Running this example results in the following:
Clicking myButton the first time- animation runs but resets when the label text is changed.
Clicking myButton the second time - animation runs but does not reset to original position.
It seems like this happens because the label text doesn't change. If I completely remove the line updating the text, this also stops the animation from resetting at the end.
I would like to fix this so that when the method runs, the label text can be updated without resetting the animation.
- (IBAction)move:(id)sender {
[UIView animateWithDuration:0.4 delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.myButton.center = CGPointMake(200, 300);
}completion:^(BOOL finished){
if(finished){
self.myLabel.text=#"moved";
}
}];
}
This problem can be caused by having Auto Layout set on the UIView. Strictly speaking, if you're using Auto Layout, then you shouldn't animate the absolute position of objects -- you should animate their constraints instead.
Changing the label text once your animation is underway triggers a layout refresh, and iOS shuffles everything around to comply with the original view constraints. (I suspect this is a behavioural change from iOS7).
Quick fix: un-check Auto Layout on the View, and this should work as expected.
Try this. Put the desired animation in the finish block also.
- (IBAction)move:(id)sender {
[UIView animateWithDuration:0.4 delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.myButton.center = CGPointMake(200, 300);
}completion:^(BOOL finished){
if(finished){
self.myLabel.text=#"moved";
self.myButton.center = CGPointMake(200, 300);
}
}];
}
I am trying to animate a view from position A to B and then back again. Previously, I would do something like the following to animate to B:
[UIView animateWithDuration:1 animations:^{
self.myView.transform = CGAffineTransformMakeTranslation(100, 0);
}];
And then this to animate back to A:
[UIView animateWithDuration:1 animations:^{
self.myView.transform = CGAffineTransformMakeTranslation(0, 0);
}];
All without needing to know the original position.
Now in Auto Layout I am using the following code to animate to position B:
self.myLeadingConstraint.constant = 100;
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Is there a way to get the previous constant value without needing to create another variable or look in IB to see what the initial value is?
Is there a better way to do all of this?
Thanks in advance.
No, that's the simplest way. Here are your options to animate autolayout:
Call layoutIfNeeded in a block. If you are just translating, change only the constant.
Animate layers instead of views.
Drop constraints, use autosizing masks.
Use a container view and animate that container.
Use constraints that don’t interfere.
Set frame in viewDidLayoutSubviews.
What you mean getting the previous constant value? isn't that on self.myLeadingConstraint.constant already?
I have the following block of code to fade out an introView(UIView)
// Hide intro view after 5 seconds
[UIView animateWithDuration: 1.0
delay: 5.0
options: (UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveLinear)
animations: ^{
introView.alpha = 0;
}
completion: ^(BOOL finished) {
[introView removeFromSuperview];
}];
I have a skip button inside the introVew but there is no interaction whatsoever, am I missing something? I have to add this is a Universal app targeting 3.2 and I'm using XCode 4.2
Pretty sure this is impossible pre-4.0:
UIView userInteractionEnabled Docs
During an animation, user interactions are temporarily disabled for
all views involved in the animation, regardless of the value in this
property. You can disable this behavior by specifying the
UIViewAnimationOptionAllowUserInteraction option when configuring the
animation.
There seems little point in targeting 3.2 in an app you haven’t released yet.
Are you setting your button alpha to 0?
If yes here is an interesting thing about animation.
What you see on the screen during the animation is not what the application sees.
The moment you set your alpha to 0, the alpha is 0 for that view, even if you are still seeing it on the screen.
Also, a view that has an alpha lower that 0.05 (don't recall the exact number) won't get touch event.
What you can do is to implement the - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event of that view's superview. or the touchesEnded... as you like.
(Assuming that your not setting it's alpha to 0.)
So you can test for touche that occur where the button is, or just remove that button and let any touch on the screen cancel your animation.
You may also be interested in this post:
Core Animation, unexpected animated position and hitTest values
I found another circumstance which could cause this. I haven't seen this answer anywhere else. It does not deal with alpha at all.
If you use a delay in the call to UIView.animate(), then even if you specify the .allowUserInteraction option, the view does NOT receive touches during the delay period. I have no idea why, but I could help it by moving the code block to another function, and using a performSelector after the same delay seconds, and in the block I run the code without delay.
I had the same problem with a button that I animated with changing the alpha. Cueing off VinceBurn's answer...
What you see on the screen during the animation is not what the application sees. The moment >you set your alpha to 0, the alpha is 0 for that view, even if you are still seeing it on the >screen.
AND view that have an alpha lower that 0.05 (don't recall the exact number) won't get touch >event.
… the simple solution of just making the minimum alpha 0.1 instead of 0.0 worked for me:
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.myButton.alpha = 0.1f;
}
completion:^(BOOL finished){
}]
Button registered the touchUpInside all the time with no additional method needed, and there was virtually no difference in appearance from taking the alpha to zero.
This won't work in iOS 3.2 since Blocks are only available in iOS4
you will have to use the standard animation techniques, in a separate thread so that you don't block the interface
[UIView beginAnimations:nil context:nil];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:view cache:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[view1 setHidden:TRUE];
[UIView commitAnimations];