I was learning the auto layout with animations from the tutorial
http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was
and things were working perfect.
When I tried to use this concept in my application, trying to animate a settings screen(a UIView) from bottom to top,it works great when the settings screen is just an empty UIView,
But in case I add a UILabel as a subview to this settings screen, the animation just vanishes.
On removing this UILabel form the settings screen, the animation is visible.
Here are the outlets that I have connected
__weak IBOutlet UIView *settingsView;
__weak IBOutlet NSLayoutConstraint *settingsBottomConstraint;
__weak IBOutlet NSLayoutConstraint *settingsViewHeightConstraint;
View did load setup method(setupViews)
-(void)setupViews
{
settingsBottomConstraint.constant = - settingsViewHeightConstraint.constant;
[settingsView setNeedsUpdateConstraints];
[settingsView layoutIfNeeded];
isSettingsHidden = YES;
}
Hide/Unhide Method
- (IBAction)showSettingsScreen:(id)sender {
if (isSettingsHidden) {
settingsBottomConstraint.constant = 0;
[settingsView setNeedsUpdateConstraints];
[UIView animateWithDuration:.3 animations:^{
[settingsView layoutIfNeeded];
}];
}
else{
settingsBottomConstraint.constant = - settingsViewHeightConstraint.constant;
[settingsView setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[settingsView layoutIfNeeded];
}];
}
isSettingsHidden = !isSettingsHidden;
}
My issue seems similar to the
Issue with UIView Auto Layout Animation
I found the answer.
Instead of,
[settingsView layoutIfNeeded];
this line made it worked like charm,
[self.view layoutIfNeeded];
I suppose we need to perform layoutIfNeeded method on the parent view not just the view we are trying to animate.
UPDATE:
As pointed out in a comment by codyko, this is required for iOS 7, iOS 10.
For iOS 8 this issue does not exists.
Related
I'm animating the change of an NSLayoutContraint to slide in a view from the top edge of the screen. I already found answers how to do this properly and it works perfectly fine in iOS8. However if I try the same code on iOS7 it just doesn't slide in my view but the view just stays at the same place instead.
Here's my code:
[self.view layoutIfNeeded];
if (_filterVisible) {
[_filterViewTopConstraint setConstant:-_filterViewHeight.constant];
_filterVisible = NO;
}else{
[_filterViewTopConstraint setConstant:0];
_filterVisible = YES;
}
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
The constants for the layout constraints have the right values, I already checked that.
Has anybody experienced a similar issue or can help me with this?
Background
I'm working on a quick and dirty notes app purely to try to understand autolayout. As such I am looking for an autolayout-specific solution to this problem.
I am quite sure that my terminology and understanding of this subject may be incorrect in places so if I misphrase or omit information through ignorance that would otherwise be helpful I am very happy to update this question with better specifics.
Short Problem Summary
This app is a simple note app. On the detail view of the note, there are two text input views, a UITextField, and a UITextView.
The goal is to use autolayout to animate a change of height to the UITextView when it is being edited (making room for the keyboard), and then animate the UITextView back to it's original size when editing is finished.
The animation code I have in place works, however when the UITextView is scrolled near to the bottom of the text the animation from "editing" size to "non-editing" size displays incorrectly durring the animation. (The final result of the animation, however is correct.)
I'm open to alternate "correct" ways of doing this if there's a common pattern for the solution. I am, however, looking for an autolayout solution which, I believe, means avoiding modifying the view's frame directly. (Could be wrong on that.)
Details and Code
A short video of the problem is available here:
http://pile.cliffpruitt.com/m/constraint_problem.mp4
This is the code performing the animation:
// self.bodyFieldConstraintBottom refers to an outlet referencing the UITextView's bottom constraint
// This animation occurrs when the text view is tapped
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
[self enterEditingMode];
[UIView animateWithDuration:2.35
animations:^{
NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
bottom_constraint.constant = 216;
[self.view layoutIfNeeded];
}];
return YES;
}
// This animation occurrs when editing ends and the text field size is restored
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self exitEditingMode];
[UIView animateWithDuration:2.35
animations:^{
NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
bottom_constraint.constant = 20;
[self.view layoutIfNeeded];
}];
return YES;
}
Full project source (in all it's messy glory) can be downloaded here:
http://pile.cliffpruitt.com/dl/LittleNotebooks.zip
Additional Comments
My understanding of cocoa terminology isn't the best so I'm having a hard time making google searches and docs searches effective. My best guess about the problem (based on observing the animation at a slow speed) is that it is related to a scroll offset somehow because unless the text is scrolled past a certain point, the problem does not manifest itself.
I have read quite a few SO question/answers including:
Resizing an UITextView when the keyboard pops up with auto layout
How to resize UITextView on iOS when a keyboard appears?
UIScrollView animation of height and contentOffset "jumps" content from bottom
The problem is that these answers either do not work ([self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)]; seems to have no effect) or appear to rely on setting the frame of the UIText view which I believe is not supposed to be done when using autolayout.
Final Side Note
I've been at this off and on for about 4 days so my understanding and recollection of all I've read and tried is actually a little less clear than when I'd started. I hope I'm explaining this well enough to convey the issue.
EDIT:
I've noticed that this code actually gets somewhat close to the desired result:
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
[self enterEditingMode];
[UIView animateWithDuration:2.35
animations:^{
[self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)];
[self.view layoutIfNeeded];
}];
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self exitEditingMode];
[UIView animateWithDuration:2.35
animations:^{
[self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[self.view layoutIfNeeded];
}];
return YES;
}
The problem with this version is that the scroll indicator scrolls down past the visible area of the text content, meaning it gets "lost" behind the keybaord. Also, it does not help me understand the correct way to animate a UITextView (UIScrollView ?) bottom constraint.
The issue looks weird and I am really not sure whats the main issue but I found out that for the best results you should call [self.view setNeedsUpdateConstraints]; before animating view.
My example code to animate view when keyboard appears:
-(void)keyboardWillShow:(NSNotification *)notification {
CGSize kbSize = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
//BH: iOS7 is messed up
CGFloat keyboardHeight = kbSize.width;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
keyboardHeight = kbSize.height;
}
self.centerYConstraint.constant = keyboardHeight;
[self.view setNeedsUpdateConstraints];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.view layoutIfNeeded];
[UIView commitAnimations];
}
I am using commit animations to animate view with same animationCurve as iOS is animating keyboard so view is moving 1 to 1 accordingly to keyboard. Also, please notice if statement for iOS8 vs iOS7 where Apple finally fixed window sizing.
A uiviewcontroller has two UIViews and one tableview added in stroyboard ib and connected with IBoutlet
when user click swipe in top uiview, I call following method to change frames of all subview IBoutlet elements. That works.
-(void)hideTopViews
{
[UIView animateWithDuration:0.25 animations:^{
[_overallHealth setHidden:YES];
_overallHealth.frame = CGRectMake(0, 0,self.view.frame.size.width,0);
[_healthBar setHidden:YES];
_sortView.frame = CGRectMake(0, 45,self.view.frame.size.width,_sortView.frame.size.height);
_portfoList.frame = CGRectMake(0, _sortView.frame.origin.x+_sortView.bounds.size.height+50,self.view.frame.size.width,self.view.frame.size.height);
_sortButton.frame = CGRectMake(90, 45,_sortButton.frame.size.width,_sortButton.frame.size.height);
[self.view bringSubviewToFront:_sortButton];
self.navigationItem.leftBarButtonItems=nil;
}];
}
However when I add another subview programmatically to self.view all subviews goes back to their original positions like in storyboard.
-(void)showPortfolioDetailsScreen
{
PortfolioDetails *portView=[[PortfolioDetails alloc] initWithFrame:CGRectMake(300,200,200,200)];
portView.backgroundColor=[UIColor redColor];
[self.view addSubview:portView];
}
How can I fix this?
#tanzolone is on right track, if you are using only portrait mode and want your storyboard to stop changes your subviews to original frames go to your storyboard and uncheck Use Autolayout
so you can use the old struts-and-springs model.
here is a detailed tutorial of autolayouts though
http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2
I have UIViewController, there is some animation in these UIViewController. UIView that animates on this UIViewController has different start and end position. I also have segue, that will push new UIViewController to the scene. All works okay, but if I return to my first UIViewController, position of my UIVIew changes to start position.
How can I fix this?
- (void)viewDidLoad
{
[super viewDidLoad];
[UIView animateWithDuration:0.3 animations:^{
self.myView.center = CGPointMake(self.myView.center.x,self.ahotherView.center.y);
}completion:^(BOOL complete){
}];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.myView.center = CGPointMake(self.myView.center.x, self.ahotherView.center.y);
}
Autolayout is causing it to return to the beginning position. You can either turn it off or write a bit of code to override the constraints.
You'll have to first add constraints in Storyboard, which is an entirely different question in itself. I recommend doing some research. Once you've added the proper constraints and created IBOutlets for them, you'll be changing the values of the constraint constants, then calling [self.myView layoutIfNeeded] inside your animation block. Instead of changing the center property. Might look something like this:
//update constraint values
self.topConstraint.constant = 70;
self.bottomConstraint.constant = 20;
//animate
[UIView animateWithDuration:0.5f animations:^{
[self.myView layoutIfNeeded];
} completion:^(BOOL finished) {
}];
I have a custom subclass of UITableViewCell. On my storyboard, it basically has a containerView (UIView *), that has some labels on it, and a UISwitch. I add a gesture to slide my tableViewCell to the right to show some detail controls underneath the containerView, and then another gesture to slide the original view back. The animation code looks like this:
- (void)showCustomControls:(UISwipeGestureRecognizer *)gesture {
[UIView animateWithDuration:0.3 animations:^{
[self.containerView setFrame:CGRectMake(self.frame.size.width - kTableViewCellSwipedWidth, 0, self.frame.size.width - kTableViewCellSwipedWidth, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
self.isReveal = YES;
self.selectionStyle = UITableViewCellEditingStyleNone;
}];
}
- (void)hideCustomControls:(UISwipeGestureRecognizer *)gesture {
[UIView animateWithDuration:0.3 animations:^{
[self.containerView setFrame:CGRectMake(-kTableViewCellBounceWidth, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
[self.containerView setFrame:CGRectMake(-kTableViewCellBounceWidth / 2, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
[self.containerView setFrame:CGRectMake(0, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}];
self.isReveal = NO;
self.selectionStyle = UITableViewCellSelectionStyleBlue;
}];
}
I noticed if I left the selectionStyle on, with the row open, it looked kind of funny. So I wanted to turn the selectionstyle off. So the code in there is to turn on and off the selection style when the row is open or closed. It works, however the weird thing is, when hideCustomControls gets called, and my original row slides back into place, the labels on the container view all go away. I have a little edge of the original row that shows, and when that is showing, the labels are there. But the second that the row slides all the way in place, all the labels disappear. The UISwitch which is also on the containerView never has problems though. It is always present. Is there a reason why this may be happening? When I remove the selectionStyle code in both methods, this problem goes away. Thanks in advance.
Edit:
I put in the correct code to animate using NSLayoutConstraint. Now the problem is the UIView subclasses I have on the view underneath show through to the top view when my row slides back into place. Not sure why this happens...