UIView animation returns to origin - ios

I have a UILabel inside of a UITableViewCell. I am trying to animate the label moving to the right when the user taps the cell. I have this code:
CGRect otherFrame = cellLabel.frame;
otherFrame.origin.x +=50;
[UIView animateWithDuration:1.0f animations:^{
cellLabel.frame = otherFrame;
}];
The odd thing that's happening is that the label is jumping 50 pixels to the left and animating back to its origin (where it was before the action began).
I actually had this working earlier in the week and didn't have any trouble with it, and after scouring through the revision history, I can't figure out where I've gone wrong. I must be missing something stupid.
EDIT:
Based on the answer from Jakub, I found that this works:
[UIView animateWithDuration:0.01f animations:^{}
completion:^(BOOL finished)
{
CGRect otherFrame = cellLabel.frame;
otherFrame.origin.x += 50;
[UIView animateWithDuration:1.0f animations:^{
cellLabel.frame = otherFrame;
}];
}];
Oddly, if I move all of the logic into a completion handler that performs a new animation after the first one completes (without the first actually doing anything), everything animates properly. This is super hacky, and I'm not at all happy with it.
Can anyone think of what would cause the initial animation to move the frame to the negative offset of the intended destination, only to animate it back to its origin, and why triggering a new animation as the completion handler of an empty animation would work?
EDIT 2:
I am going to mark Duncan's answer as the right one because it pointed me in the right direction, but I am still baffled why/how these symptoms can happen:
The animation moves the frame to the negative offset of the
destination, then animates it back to the origin (rather than from
the origin to the destination)
Running an empty animation block but adding another animation to the first block's completion handler animates correctly
Info for the attempted answers:
I have to use auto layout for the project, and as far as I know, I
can't disable it for a single view
I am not putting anything onto a background thread. I didn't try specifically dispatching to the main thread, but I think I was already there. Isn't all visible UI always on the main thread? It was animating, just not as expected.
The reason I didn't go the route of changing constraints to begin with is that I am using prototype cells and IB doesn't let you create IBOutlets from prototype cells. There's a bit of work to walk a constraint list and detect a specific one, so I left the constraints blank and tried to animate the frame (and as I said, it was working earlier in the week -- and still worked when animating from an animation block's completion handler).
So the final solution was to add constraints to the container cell (the important one here being the leading), then to animate, I had to find the constraint:
NSLayoutConstraint *titleLeadingConstraint = nil;
for( NSLayoutConstraint *constraint in cellLabel.superview.constraints )
{
if( constraint.firstItem == cellLabel && constraint.firstAttribute == NSLayoutAttributeLeading )
{
titleLeadingConstraint = constraint;
}
}
Then set the constraint constant:
titleLeadingConstraint.constant = 55.0;
Then set the animation block:
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
This solution should be more future proof (and robust, reliable and stable) than moving the frame, but it turned out to be a fair amount of work in discovery.

Do you have auto layout set in your storyboard or XIB by mistake? (It is on by default). If so you either need to turn AutoLayout off or animate a constraint rather than manipulating your view's frame.

Where are you calling this code?
Try calling UIView animateWithDuration:... method on the main thread, in a
dispatch_async(dispatch_get_main_queue(), ^{
// Your animation here
});
If this method is not executed on the main thread, it usually jumps to the final stage of the animation.

You should reset the origin after animation.

Related

prevent to view appear with animmation

I have a problem that my custom navigation bar appears for the first time with animation. I think it happens because I use auto-layout and it animates it self into the state of landscape or portrait. But I want to have a functionality, that after first time I enter the screen everything is still, and where it belongs. and after that if I turn the screen or if I do something all the animations appears like now.
Is there a good think to omit the first animations when the view creates itself?
The animations are : labels floating from left to right. and 1 label appears as from 0px width and height it scales into 100% width and height
code:
[self.navigationController.navigationBar addSubview:self.topBar];
self.topbar is UIView. I have added it to block [UIView performWithoutAnimation:^{ }]; but it does not helps, everytime the view appears for the first time I have my labels floating from left to right.
The answer is that I used some methods [self layoutIfNeeded]; After setting the constraint. The solution is to have a method set like this:
- (void) yourAnimation:(BOOL)animated{
if(animated){
[UIView animateWithDuration:0.3 animations:^{
//your animated code
//[self layoutIfNeeded];
}];
}else{
[UIView performWithoutAnimation:^{
//your stuff without animation
}];
}
}

How to create an animation like in Facebook Pages top menu?

In facebook'a new app, Pages, when you swipe down the menu appears at the top. The animation I am trying to do is the animation when you tap the settings button which is when each cell moves to the left one at a time, but very smoothly. How can I achieve such an animation without using Facebook's pop engine in Objective C ?
Bear in mind that each cell is probably a custom subclass of UIView (i.e. not UITableViewCell or other apple class) so you will have to design your UI carefully.
The animation, however, is easily achievable using the UIView class. Apple's documentation is a good place to start to learn about animation.
What you want is something like:
NSTimeInterval delay = 0.2;
for (UIView *myView in myViewArray) {
[UIView animateWithDuration:0.5 delay:delay options:UIViewAnimationOptionCurveEaseInOut animations:^{
/*
* This is where you set the properties you want to animate.
* If you're using AutoLayout (i.e. NSLayoutConstraint) you need to set the constant property of the relevant constraint, not the view's frame property.
*/
} completion:nil];
delay += 0.2;
}
This will provide you with animations that occur one after the other (0.2 seconds apart).

Object returns to the original position (after an animation) when keyboard appears

I have this strange new issue: this code (that works perfectly)
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
CGRect frame = _logoClaim.frame;
frame.origin.y -= 180;
_logoClaim.frame = frame;
} completion:NULL];
moves a view that contains a UIImageView and an UILabel to the top of my self.view.
The view that moves unhides a UITextField.
When I try to write text into the UITextField, obviously appear the keyboard.
And at this moment, the view animated before, returns to the original start position!!!
What is the reason?
Put a completion block in your animation and check the finished value. The keyboard is cancelling the animation and the finished bool will be NO. I would disable input in the UITextField until the animation is completed. Do this in your completion block.
EDIT
Looking at your duration and re-reading the question,I may be mistaken with what I think you mean. If this answer is incorrect I will remove it.
Also, search your code for _logoClaim.frame in case you are adjusting it onKeyboardWillAppear
You would need to create outlet for its bottom/top space constraints and update the constant value of it inside the animation block.
[UIView animateWithDuration:0.4
...
animations:^{
/*Update the value of the constraint outlet supposing it to be a bottom space constraint*/
_layoutConstraintBottom += 180;
[self.logoClaim layoutIfNeeded];
} completion:NULL];
Do not forget to call -layoutIfNeeded for your animating view.

How to translate an entire UIView and retain gesture recognition?

I have a UIView "MainView" that initially appears as follows:
The gradient bar is part of MainView, the whitespace beneath is part of a Container View subview.
When the search button in top-right is tapped, I animate a searchBar from offscreen to be visible in the view:
I manage to do this by the following code:
CGRect currentViewFrame = self.view.bounds;
currentViewFrame.origin.y += searchViewHeight;
[UIView animateWithDuration:0.4
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:4.0
options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.view.frame = currentViewFrame;
}
completion:^(BOOL finished)
{
}];
Visually, the result of this animation is perfect. The entire view shifts, and the searchBar is now on screen. However, the searchBar does not respond to interaction. Correct me if I'm wrong, but I expect this is because the MainView's frame no longer includes the screen area that the searchBar now occupies, so its effectively a gesture deadzone.
So this makes me think that instead of lazily animating the entire MainView down to accomodate the searchBar, I must instead individually translate all subviews of MainView one at a time. In this simple situation, that would not be a big problem, but I can envision a circumstance with tens of subviews making that completely unrealistic.
What is the best method to accomplish what I am trying to do? Is there a secret to animating entire views/subviews without having gesture deadzones? Thanks in advance!!

UIView's frame gets overridden by Storyboard on initial load

I need to change the width of a subview depending on, say, available disk space. So I have a selector that's called upon the view controller's viewWillLayoutSubviews as such:
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageView setNeedsDisplay];
Really, I just want to change the width of the subview. It works if the view controller is the initial controller; however, if it is transitioned from, say, a Push Segue it stopped working. What happens is I'd see my desired width rendered for moment but then it gets changed to the original width as per its blueprint on the Storyboard.
The behavior feels like the iOS caches the original (storyboard) view in a block during the push segue animation, and upon completion it executes the block which doesn't have my calculations above; thereby overridden the desired view. Any idea?
There are two approaches.
The first approach is to circumvent the problem, with a side benefit of cool animation. Since I have no clue when or how many times or exactly how iOS enforces the auto-constraints, I simply delay my UIView modifications and smooth it out with animation. So I wrap view animation around the code in my selector:
[UIView animateWithDuration:.25 animations:^{
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageView setNeedsDisplay];
}];
Then I'd call my selector in my controller's viewDidAppear:animated:
[self performSelector:#selector(resetUsageView) withObject:self afterDelay:0.0];
Now most would say this is a cop-out or a kludge. And that's fair.
In the second approach I attack the root of the problem - auto layout/constraints. I add a Width constraint to my usageView in the storyboard, and connect its IBOutlet to my code.
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *usageViewWidthConstraint;
Then I modify the constraint as per the calculated width in my controller's viewWillAppear:animated:
[self.usageViewWidthConstraint setConstant:self.someCalculatedWidth];
Voila! A one liner.
Now what if I want animation given I got a taste of it from the first approach? Well, constraint changes are outside the realm of animation so basically I'd need a combination of the first and second approaches. I change the UIView's frame for animation, and modify the constraint as well to "set the record straight":
[UIView animateWithDuration:.25 animations:^{
CGRect rect = self.usageView.bounds;
rect.size.width = self.someCalculatedwidth;
[self.usageView setFrame:rect];
[self.usageViewWidthConstraint setConstant:self.someCalculatedWidth]; // <<<<
[self.usageView setNeedsDisplay];
}];

Resources