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.
Related
I am using the following code to add a overlay view to a view:
UIView *overlayView = [[UIView alloc] init];
overlayView.backgroundColor = [UIColor redColor];
[self.view addSubview:overlayView];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(overlayView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[overlayView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[overlayView]|" options:0 metrics:nil views:views]];
If I place this code in the viewDidLoad method of a UIViewController it works fine. However, if I do the same in a UITableViewController the overlay view gets a zero frame.
I have inspected the view and I can se the constraints do get added correctly and they are active. But for som reason they seem to be ignored.
I don't get any error or warning.
What am I missing here?
PS: I know I can instantiate the overlay view with self.view.bounds as frame, and it works. However, I need to use autolayout.
Suspect it's because the UITableView is a UIScrollView and auto layout has a few caveats when working with views that establish their own bounds systems (such as the scroll view or table view).
The constraints set on the overlay view are such that it is constrained to the top, right, bottom, and left edges of the scroll view, and thus to the content area of the scroll view rather than the scroll view's frame.
See
https://developer.apple.com/library/ios/technotes/tn2154/_index.html
and Auto Layout Guide: Working with Scroll Views:
Constraints between the edges or margins of the scroll view and its
content attach to the scroll view’s content area.
Instead of constraining overlay view to the content area, constrain it to the scroll view's frame instead. According to the above references, this can be done either by constraining overlay view's width/height to the scroll view, or by constraining the overlay view to a view outside of the scroll view.
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
I'm adding a login view in my tableview. But as you can see on the image below it doesn't look great.
I am using auto layout for both the tableview and the loginview.
For the loginview I have the constraints showed on the image below, but what's strange(to me) is that these constraints seem to be valid for both the Green View and its superview (the black). Because, when I change it for one of them, it changes for the other as well.
Note: The loginview has separate a controller, but the controller is not presented. The loginview is just added to the tableview.
Any suggestions?
From what I can see in this image, there are 4(!) views you need to account for.
1.) the superview
2.) the UITableview
3.) the black view
4.) the login-view (blue-green?)
It looks like the constraints for (4) are set correctly in relation to (3)
To me, I'd examine the constraints for (3) in relation to (1).
In other words make sure the leading/trailing top/bottom constraints are all 0 or to margin in relation to the superview, and you should be good to go.
Here is a quick example on how you could implement this. This assumes you are using view controller containment, which you should be.
child is your signIn view and self is the Table view controller
// Add login view to my view
[self addChildViewController:child];
child.view.frame = self.view.bounds;
[self.view addSubview:child.view];
[child didMoveToParentViewController:self];
// Add constraints
NSDictionary * views = #{#"view" : child.view, #"super" : self.view};
NSArray * hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[view]-|" options:0 metrics:nil views:views];
NSArray * vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[view]-|" options:0 metrics:nil views:views];
[self.view addConstraints: [hConstraints arrayByAddingObjectsFromArray:vConstraints]];
I want to place a UITableView such that it takes up the entirety of one of my view controller's subviews. Here's the view hierarchy, where self is my UIViewController subclass:
[self.view] -> [self.rootView] -> [self.tableView]
I know that I need to set setTranslatesAutoresizingMaskIntoConstraints to NO for any view whose subviews I want to lay out with Auto Layout, so here's my viewDidLoad.
[super viewDidLoad];
self.rootView = [[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, 320.0, 568.0)];
[self.rootView setTranslatesAutoresizingMaskIntoConstraints:NO];
So far so good. Now I want to add a UITableView.
self.tableView = [[UITableView alloc]init]; // same thing with initWithFrame:CGRectZero
[self.rootView addSubview:self.tableView];
No problem so far, but I can't see the table view (because it doesn't have a frame). Let's add some constraints:
[self.rootView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[table]-0-|" options:0 metrics:nil views:#{#"table": self.tableView}]];
[self.rootView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[table]-0-|" options:0 metrics:nil views:#{#"table": self.tableView}]];
Everything breaks. self.rootView.backgroundColor is green and the cells are blue and full of text, but there's nothing on the screen but grey, maybe indicating that self.view is no longer in the view hierarchy? I've done a bunch of googling but can't find a real solution anywhere. I've also tried adding all the constraints one at a time the other way and the same thing happens. Does anyone have any ideas?
I believe you should be calling setTranslatesAutoresizingMaskIntoConstraints:NO on your UITableView instead of on self.rootView. But as rdelmar commented, first make sure your table view is not what you are seeing with the grey background since it will fill the screen and cover the other views.
And another note, I believeself.rootView = [[UIViewController alloc]initWithFrame...] above actually should be [[UIView alloc] initWithFrame...] since controllers don't have frames but maybe it's just incorrect above as this would cause an error when trying to run.
I have this view that used to have autoresizingMask = UIViewAutoresizingFlexibleHeight
When the status bar would animate its height (like when hanging up a phone call), the view's height would animate and increase.
But with auto layout I'm replacing this autoresizingMask with constraints:
UIView *orangeView = [[UIView alloc] initWithFrame:CGRectZero];
orangeView.translatesAutoresizingMaskIntoConstraints = NO;
orangeView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:orangeView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[orangeView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(40)-[orangeView]-(190)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];
But now, the change in my layout is not animated with the status bar, it's just changed without any animations.
Now I know that I should call -layoutIfNeeded in an animation block when using constraints-based layout. But here I'm not the one creating the animation block! So is there a way to animate the change?
Does it mean I have to found a place in my code that would be executed during this animation block I didn't initiate? I tried to set [self.view layoutIfNeeded] in my controller when the UIApplicationWillChangeStatusBarFrameNotificationis fired, but it doesn't work.
Make sure you add your constraints in the updateConstraints method.
Here's what the docs say:
Custom views that set up constraints themselves should do so by overriding this method. When your custom view notes that a change has been made to the view that invalidates one of its constraints, it should immediately remove that constraint, and then call setNeedsUpdateConstraints to note that constraints need to be updated.