Using storyboard, I've set up multiple views encompassed by a scroll view like this
Everything under the "More Symptoms" button is a view (except the Collection View behind it). Let's call it moreSymptomsView. Now what I'm trying to do is when the More Symptoms button is tapped, I'd like to shift moreSymptomsView down with setFrame and reveal the collection view behind it by setting the hidden property to false.
It functions properly, but after trying to scroll, the moreSymptomsView goes back up to its original place like here (I'm assuming due to it's constraints).
How should I go about resetting the constraints of moreSymptomsView to the bottom of the new collection view programmatically?
Thanks!
If you want change frame when using AutoLayout. You should change constant of Constraint. If you setFrame It will auto back to previous state if have interaction with UI. With your case you can do like this:
Drag drop top constraint of More Symptoms:
name for it. I named it to constraintTopSympotomsLabel
When you want change frame it:
self.constraintTopSympotomsLabel.constant = ValueYouWant
for moving it to new frame.
You can change all of constraints with the same way to achieve frame you want.
Hope this help!
For anyone that sees this question in the future, here's what I did to solve this issue:
- (IBAction)showMoreSymptoms:(id)sender {
if(!moreSymptomsExpanded) {
moreSymptomsExpanded = true;
[_moreSymptomsCollectionView setHidden:false];
[_moreSymptomsButton setTitle:#"Less Symptoms" forState:UIControlStateNormal];
//change frames
[_moreSymptomsView setFrame:CGRectMake(_moreSymptomsView.frame.origin.x, _moreSymptomsView.frame.origin.y+_moreSymptomsCollectionView.frame.size.height, _moreSymptomsView.frame.size.width, _moreSymptomsView.frame.size.height)];
//change constraint to the bottom of the new collection view
_higherPriorityMoreSymptomsViewConstraint = [NSLayoutConstraint constraintWithItem:_moreSymptomsView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_moreSymptomsCollectionView
attribute:NSLayoutAttributeBottom
multiplier:1
constant:1];
_higherPriorityMoreSymptomsViewConstraint.priority = 1000;
[_moreSymptomsView.superview addConstraint:_higherPriorityMoreSymptomsViewConstraint];
[UIView animateWithDuration:0 animations:^{
[self.view layoutIfNeeded];
}];
} else {
moreSymptomsExpanded = false;
[_moreSymptomsCollectionView setHidden:true];
[_moreSymptomsButton setTitle:#"More Symptoms" forState:UIControlStateNormal];
//reset frames back
[_moreSymptomsView setFrame:CGRectMake(_moreSymptomsView.frame.origin.x, _moreSymptomsView.frame.origin.y-_moreSymptomsCollectionView.frame.size.height, _moreSymptomsView.frame.size.width, _moreSymptomsView.frame.size.height)];
//reset back to original constraint
[_moreSymptomsView.superview removeConstraint:_higherPriorityMoreSymptomsViewConstraint];
[UIView animateWithDuration:0 animations:^{
[self.view layoutIfNeeded];
}];
}
}
I set the original top constraint's priority on storyboard to a lower priority (750).
When the moreSymptomsButton is tapped, I shifted the frame of the moreSymptomsView down as expected
I created a new top constraint called higherPriorityMoreSymptomsViewConstraint and set the toItem attribute to the new collection view that I'm showing (moreSymptomsCollectionView) with a higher priority
To collapse back to the original state, reset the frame and remove the constraint created earlier.
For more of an explanation, check this link out
Related
I've got two UIButtons into a view, which is 232px wide.
The buttons are aligned such as
|[BUTTON1][BUTTON1]|
|-------232px------|
I have a set of constraints for this alignment, but now I want to be able to change the frame of BUTTON1's to full width, depending on a condition, so it'd have to be programmatically. I tried changing the frame, but since it's automatically updated, it won't work. Basically, what I want to achieve is BUTTON1 covering BUTTON2, Is there an easy way to do this?
As you are using autoLayout in your project, so to change the frame of your button, you have to change the constraint of you UIButton.
First make the IBOutlet of your buttons' width constraint and then when you want to change the frame of the button update that constraint like this
button.widthConstraint.constant = //set the constraint;
[UIView animateWithDuration:0.25f
animations:^{
[self.view setNeedsLayout];
}
completion:^(BOOL finished) {
}];
Make the outlet of button and width constraint and then
self.buttonWidthConstraint += 20 // adjust as per requirements
[self.myButton updateConstraints]
You can set the constant of the button you want covered to 0 and animate the change. Then the covering button will fill in the whole space.
I want to animate from a height of 0 to the height of the text (UILabel). I am using autolayout and I do not know how high the text will be. My approach was to start by setting a height=0 constraint to the text, and animate like this:
//retrieves the height constrain of the clicked item
NSLayoutConstraint *heightContraint = [heightConstraints objectAtIndex:sender.tag];
//activates/deactivates the constraint
heightContraint.active = !heightContraint.active;
//animates
[UIView animateWithDuration:3 animations:^{
[self layoutIfNeeded];
}];
My problem is, that with this approach, the text height does not animate, it changes from a height of 0 to the new height instantly. Only the position/size change of the containing views is animated. How I animate the text height change without knowing the height of the text??
I have found a solution. Instead of setting a constraint to the height, I set a containing view (clipping), which contains the UILabel and the view above. Then, I create 2 constraints that pin the bottom edges to the containing view, one related to the UILabel, and one related to the view above. I just activate/deactivate those constraints like this:
//get the constraints
NSLayoutConstraint *viewAboveTextConstraint =
[viewAboveTextConstraints objectAtIndex:sender.tag];
NSLayoutConstraint *uilabelContraint = [uilabelContraints objectAtIndex:sender.tag];
//flip the active states
viewAboveTextConstraint.active = !viewAboveTextConstraint.active;
uilabelContraint.active = !uilabelContraint.active;
//animate
[UIView animateWithDuration:.3 animations:^{
[self layoutIfNeeded];
}];
I have a view that automatically adjusts it's height based on number of lines in a UILabel within the view. There is another view which height is pinned to be equal to the view with the label.
I would like to animate the height change caused by setting long text to the label, thus changing the number of lines and causing autolayout constraints to recalculate height. This new height will change the height of the second view too. How can I animate autolayout changes that happen as a side effect of property assignment?
I tried this, but it did not work:
[UIView animateWithDuration:0.3 animations:^{
//I want the side effect of this assignment to be animated
self.viewWithLabel.title = #"This long title will change the view height and cause layout change";
}];
See the documentation
Try:
- (void)viewDidAppear:(BOOL)animated {
[self.view layoutIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
self.viewWithLabel.title = #"This long title will change the view height and cause layout change";
[self.view layoutIfNeeded];
}];
}
I think you want yourTextLabel.clipToBounds = YES also.
If you want more advanced effect, see this question.
I'm trying to add a view (messageView) to my menu item (messageViewMenu) so that when the menu item is tapped, this new view is added just above it and they both slide down the screen together one behind the other - Evernote 5.1.2 has something similar.
I'm using an NSLayoutConstraint to attach the new view to what I assume is the top of the menu item. I then animate the menu item's existing vertical constraint to a header UIView (_headerMessageConstraint) to grow to size 300. I would expect the new view to be attached vertically with its trailing edge to the leading edge of the menu item and they slide down the screen together.
However, the new view slides down with the menu item and then continues on behind it until it seems the top edges are aligned.
Can anyone tell me where I'm going wrong?
Thanks in advance for your help
Steve
EDIT. Since I posted this I've learnt that that when I add the new view (MessageViewController's mvc.view) it's placed by default at 0,0. I don't want it at 0,0 though so it seems like I have to either set a frame for it at the location I want it to appear at - which seems wrong when using auto layout - or add this new view to a subview and animate the subviews height, perhaps... though I may be talking myself into a dark place here.
MessageViewController *mvc = [[MessageViewController alloc] init];
[mvc.view setTranslatesAutoresizingMaskIntoConstraints:NO];
UIView *messageView = mvc.view;
[self.view addSubview:messageView];
UIView *messageViewMenu = self.messageViewMenu;
NSDictionary* views = NSDictionaryOfVariableBindings(messageViewMenu, messageView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[messageView]|"
options:0
metrics:nil
views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[messageView(200)]-[messageViewMenu]"
options:0
metrics:nil
views:views]];
[self.view layoutSubviews]; // UPDATE - THIS IS THE CODE THAT WAS MISSING - IT DISPLAYS THE SUBVIEWS BEFORE THE ANIMATION STARTS. THANKS #RDELMAR
[UIView animateWithDuration:0.9 animations:^{
_headerMessageConstraint.constant = 200;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){}];
I'm not sure exactly what you're trying to do, but you need to add the line [self.view layoutSubviews] just above your animation block. The main view needs to layout your new view in its starting position, before you animate.
You also need to take out the dash in "V:[messageView(200)]-[messageViewMenu]" if you want the 2 views right on top of each other with no space.
I've never worked with autolayout constraints before. I have a small new app I'm working on and noticed that the NIB's views are defaulting to autolayout. So, I figured I'd take the opportunity to work with it and try to figure out where Apple is going with this.
First challenge:
I need to resize an MKMapView and I'd like to animate it to the new position. If I do this the way I'm used to:
[UIView animateWithDuration:1.2f
animations:^{
CGRect theFrame = worldView.frame;
CGRect newFrame = CGRectMake(theFrame.origin.x, theFrame.origin.y, theFrame.size.width, theFrame.size.height - 170);
worldView.frame = newFrame;
}];
...then the MKMapView will 'snap' back to its original height whenever a sibling view gets updated (in my case a UISegmentedControl's title is being updated [myUISegmentedControl setTitle:newTitle forSegmentAtIndex:0]).
So, what I think I want to do is change the constraints of the MKMapView from being equal to the parent view's hight to being relative to the top of the UISegmentedControl that it was covering: V:[MKMapView]-(16)-[UISegmentedControl]
What I want is for the MKMapView height to shorten so that some controls beneath the map view are revealed. To do so I think I need to change the constraint from a fixed full size view to one where the bottom is constrained to the top of a UISegmentedControl...and I'd like it to animate as view shrinks to new size.
How does one go about this?
Edit - this animation is not animating though the bottom of the view does move up 170 instantly:
[UIView animateWithDuration:1.2f
animations:^{
self.nibMapViewConstraint.constant = -170;
}];
and the nibMapViewConstraint is wired up in IB to the bottom Vertical Space constraint.
After updating your constraint:
[UIView animateWithDuration:0.5 animations:^{[self.view layoutIfNeeded];}];
Replace self.view with a reference to the containing view.
This works for me (Both iOS7 and iOS8+). Click on the auto layout constraint you would like to adjust (in interface builder e.g top constraint). Next make this an IBOutlet;
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
Animate upwards;
self.topConstraint.constant = -100;
[self.viewToAnimate setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.viewToAnimate layoutIfNeeded];
}];
Animate back to original place
self.topConstraint.constant = 0;
[self.viewToAnimate setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.viewToAnimate layoutIfNeeded];
}];
There is a very good tutorial from apple itself that explain how to use animation with autolayout.
Follow this link and then find the video named "Auto layout by example"
It gives some interesting stuff about autolayout and the last part is about how to use animation.
I have made this small demo available. It shows how auto-layout constraints can be changed and animated in a very simple example. Simply take a look at the DemoViewController.m.
Most people use autolayout to layout items on their views and modify the layout constrains to create animations.
An easy way to do this without a lot of code is creating the UIView you want to animate in Storyboard and then creating a hidden UIView where you want the UIView to end. You can use the preview in xcode to make sure both UIViews are where you want them to be. After that, hide the ending UIView and swap the layout constraints.
There is a podfile for swapping layout constrains called SBP if you don't want to write it yourself.
Here's a tutorial.
No need to use more IBOutlet reference of the constraint instead of this you can directly access or update already applied constraint either applied by Programmatically or from Interface Builder on any view using the KVConstraintExtensionsMaster library. This library is also managing the Cumulative behavior of NSLayoutConstraint.
To add Height Constraint on containerView
CGFloat height = 200;
[self.containerView applyHeightConstrain:height];
To update Height Constraint of containerView with animation
[self.containerView accessAppliedConstraintByAttribute:NSLayoutAttributeHeight completion:^(NSLayoutConstraint *expectedConstraint){
if (expectedConstraint) {
expectedConstraint.constant = 100;
/* for the animation */
[self.containerView updateModifyConstraintsWithAnimation:NULL];
}
}];