move button towards target using constraints, no reaction? - ios

I would like the red button to be animated towards the leading position of the second button :
Some examples showed how to change the "constant" with numbers, but I would like to put automatically at the leading position of the second button.
I tried this, but the red button does not move, the animations log is correctly called though :
- (void)updateConstr{
NSLayoutConstraint *newLeading = [NSLayoutConstraint
constraintWithItem:self.redB
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.secondButton
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0f];
self.leadingConstraint = newLeading;//is an IBOutlet pointing on the constraint (see the image)
[self.redB setNeedsUpdateConstraints];
[UIView animateWithDuration:0.5 animations:^{
[self.redB layoutIfNeeded];
NSLog(#"animations");//is called, but the red button does not move
}];
}
- (IBAction)firstAction:(id)sender { //after a click on button "one"
NSLog(#"firstAction");
[self updateConstr];
}

This must do it:
- (void)updateConstr{
NSLayoutConstraint *newLeading = [NSLayoutConstraint
constraintWithItem:self.redB
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.secondButton
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0f];
[self.redB.superview removeConstraint: self.leadingConstraint];
[self.redB.superview addConstraint: newLeading];
self.leadingConstraint = newLeading;
[UIView animateWithDuration:0.5 animations:^{
[self.redB layoutIfNeeded];
}];
}

What I typically do in this situation is the following.
Add two constraints to your scene. One where it's aligned left between the button and the "one" label. The second, where it's aligned left between the button and the "second" label (i.e. both values will be 0). These constraints will initially conflict with one another, and that's fine.
Add IBOutlets to your view controller for the NSLayoutConstraints and assign the two constraints we've created to those IBOutlets.
Set the constraint priority on your initial condition to 999 (i.e. constraint on left align to "one" should be 999). Set the constraint priority on the destination constraint to 998 (i.e. constraint on left align between button to "second" is 998). You'll now see that these constraints will no longer conflict. This is because the priority on one constraint overrides the other.
You may see where this is headed now. So when you want to animate the button between the constraints, swap the priorities and animate!
Code:
#interface MyViewController ()
#property (nonatomic, weak) NSLayoutConstraint* constraint0;
#property (nonatomic, weak) NSLayoutConstraint* constraint1;
#end
- (void)someMethodWhereIWantToAnimate
{
NSInteger temp = constraint0.priority;
constraint0.priority = constraint1.priority;
constraint1.priority = temp;
[UIView animateWithDuration:1.0 animations:^{
// Simplest is to use the main ViewController's view
[self.view layoutIfNeeded];
}];
}

Related

How do I animate constraint changes if constraints are created programmatically with VFL?

I have trouble on something, I would be grateful if you could give me some advice.
Basically, I created some constraints with Visual Format Language and makes my base more dynamic. I didn't touch my views on storyboard.
You can consider red area as a navigationBar.
In example view looks like;
My problem is; I want to hide my topView on some viewController.
First I created a property;
#property (strong, nonatomic) NSLayoutConstraint *constraintTabbarHeight;
But I realized I couldn't set my NSLayoutConstraint property with VFL. Because VFL is create an array of constraints.
So this is my first question;
Is there anyway to keep my VFL constraint with NSLayoutConstraint property?
Anyway, so I decided to create my height constraint with constraintWithItem method.
self.constraintTabbarHeight = [NSLayoutConstraint constraintWithItem:self.tabbarContainerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:95];
Second, I search on stackoverflow and I have found a method. I can call this method from any viewController I want.
- (void)updateTabbarConstraintWith:(int) heightValue{
self.constraintTabbarHeight.constant = heighValue;
[UIView animateWithDuration:0.3
animations:^{
[self.view layoutIfNeeded];
}];
}
If I set self.constraintTabbarHeight.constant = 0 ,everything is fine. My TopView (redArea) is disappearing but PlaceholderView is still on same position.
I thought [self.view layoutIfNeeded] will update my placeholder View and it fills my screen.
It looks like this;
How do I make it correct?
Thanks for your answers.
You need to create a constraint from your placeholderView.Top to your TopView.Bottom, otherwise, one will be able to move without impacting the other one

Add subview that's docked to the bottom of superview (custom keyboard)

I'm attempting to create a custom view that appears exactly like the keyboard, but I can't figure out how to use frame manipulation and/or programmatic auto layout to solve my problem.
Some context: I have a messaging app style view controller with a textview and button docked at the bottom of the screen. All views are wrapped into a nice single content view with autolayout set such that when the keyboard appears, it pushes the entire view up, and when it disappears it pushes the entire view back down. This is the behavior I'm trying to reproduce.
I messed around with trying to manually resize frames similar to my keyboard code but ended up throwing that away in favor of a auto-layout based solution. Here's what I have so far:
StickersCollectionViewController *stickerController = [self.storyboard instantiateViewControllerWithIdentifier:#"StickersCollectionViewController"];
[self addChildViewController:stickerController];
[self.view addSubview:stickerController.view];
[stickerController didMoveToParentViewController:self];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:stickerController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:240.0];
[self.view addConstraint:width];
[self.view addConstraint:height];
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
constraint.constant = 240.0;
[UIView animateWithDuration:0.3
animations:^{
[self.view layoutIfNeeded];
}];
});
So far, this looks great: the view is instantiated and added just off screen, then animates into view. However, I'd also like my superview (mentioned above) to also animate with this view. This is the piece I need help with.
Can anyone offer help in this direction? Or offer a suggestion as to a different route I could go? Thanks.
Try this instead, the difference is using "[self.view layoutIfNeeded];" both before the animation and after the animation, and then placing this "constraint.constant = 240.0;" in the animation.
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3
animations:^{
constraint.constant = 240.0;
[self.view layoutIfNeeded];
}];
});
So, try this as well:
this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
shoud probably be this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
because you are constraining to the top of the view and not constraining to the top of the view + self.view.bounds.size.height, not sure if this helps, but this is one idea.
A second possible solution is this, wrap up all UI elements into a UIView and set this UIView as the view of stickerController.view, this isn't so straight forward because technically you should assign this view as the view of stickerController.view in a load view method in the stickerController impelemntation file and then type cast the stickerController.view as this for example
Typcasting inside implementation file of stickerController
- (void)loadView
{
[self setView:[ContainerViewThatHasSubViews new]];
}
- (ContainerViewThatHasSubViews*)contentView
{
return (id)[self view];
}
then, create a subclass of ContainerViewThatHasSubViews like so
ContainerViewThatHasSubViews.h
#interface ContainerViewThatHasSubViews : UIView
#property (nonatomic) UIView *subContainerOfContainerViewThatHasSubViews;
#property (nonatomic) NSLayoutConstraint *tester;
#end
ContainerViewThatHasSubViews.m
#interface ContainerViewThatHasSubViews ()
#end
#implementation ContainerViewThatHasSubViews {
}
self = [super initWithFrame:frame];
if (self) {
_subContainerOfContainerViewThatHasSubViews = [UIView new];
[_subContainerOfContainerViewThatHasSubViews setTranslatesAutoresizingMaskIntoConstraints:false];
[self addSubview:subContainerOfContainerViewThatHasSubViews];
/// PSUEDO CODE HERE NOW
ALL OTHER UI ELEMENTS ARE ADDED TO THE UIVIEW "subContainerOfContainerViewThatHasSubViews"
like this *** [subContainerOfContainerViewThatHasSubViews addSubView:**another ui element***];***
etc. etc. etc.
then use layout constraints like this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[subContainerOfContainerViewThatHasSubViews.view addConstraint:constraint];
etc. etc., so the layout constraints are added to "subContainerOfContainerViewThatHasSubViews"
add separate constraints to this UIView for the items inside this view, and you can then animate these cosntraitns as well by declaring the constraints in your header file as propertyies like i did with the _tester constraint, you can animate these when you press a button in your UIViewController impelmentation file by addding a gesture recognizer or whatever you have to show the keyboard
then end with this:
NSDictionary* views = NSDictionaryOfVariableBindings(subContainerOfContainerViewThatHasSubViews);
NSDictionary* metrics = #{#"sp" : #(heightResizer(10)), #"sh" : #40}; //include sizing metrics here
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_subContainerOfContainerViewThatHasSubViews]|" options:0 metrics:metrics views:views]];
and this:
_tester =[NSLayoutConstraint constraintWithItem:_subContainerOfContainerViewThatHasSubViews attribute:NSLayoutAttributeCenterTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterTop multiplier:1.0f constant:0.0f];
[self addConstraint:_tester];
}
return self;
}
#end
Gist:
https://gist.github.com/anonymous/c601481d24ad1b98b219
https://gist.github.com/anonymous/b22b68d4bf8d7fa51d66
https://gist.github.com/anonymous/a9aaf922e0f5383256b6
https://gist.github.com/anonymous/fc6655ea8200cda9c0dd
There's a lot to this one, but it's just how programatic views work. I've never use storyboard because I feel like I have more control doing everything programatically, if you can handle how much code you'll need to write to make this happen then you are good to go, I know this because I'm building an app just like you that does the same thing that you want yours to do, and this works for me because I'm digging deep into the views. This is complete control, just make sure you make your other NSLayoutContraints as properities of the subclassed UIView, and then you can place methods in your subclassed UIView's header file that point to he implementation file of the UIView subclass and these methods can be the animation methods that you can call from your view controller like this:
[[self contentView] CALLYOURFUNCTION];
to access the other layout constraints and ui elements from the subclassed UIView in your View controller do this to call them:
[[self contentView] tester]; //this is the constraint from9 the header file of tbhe custom UIView
to set the constant in your UIViewController do this:
[[[self contentView] tester] setConstant:A_NUMBER];
The point is this, you are encapsulating all your views into a UIView subclass rather than accessing the view of your view controller itself and trying to animate it's view. I've not ever seen someone try to animate the "view" property of a UIViewController itself, I've only seen the animation of a UIView that is a subview of the UIViewController's view. The Point also is that you are forcing this UIView to be the view of the UIViewControllers view and then you are using another UIView within the subClassed UIView to animate the entire Contents of the view, so it's like this:
-UIViewController's view
Sub Class of UIView
UIView <=== this is the view that contains ALL THE OTHER UIElements of the View controller
Add a constraint to the UIView that contains all other UI elements, this contraint is the tester constraint I added to the header of the custom Sub class UIView.
You can then add additional constraints to the subViews of the the UIView that I've mentioned which contains all other UI elements of the UIViewController
When you add constraints on these sub items, you can animate them as well. This method is advanced so ask questions if you have them, my answer is a little messy.
daddy warbucks answer did indeed get me close to the solution I was looking for. For those of you who have built most of your UI skeleton with the storyboard, I ended up adding a container view to the bottom of my view controller and modifying the center values of the main content view and container view on button press.
Layout content view as you normally would. Set Leading, Trailing, and Top constraints to the content view, and a Bottom constraint to the container view (described in step 2).
Create a container view and dock it below the content view. Set equal width to the content view, Top constraint to the Bottom of the content view, and a height of 240 (or whatever you wish). Note: the view will be positioned OFF the view controller. If you're slightly OCD like me, this will bother you for a little bit until you realize that it's k.
Here's what I used to move the container view into place and have the content view follow with it. Note that I probably will clean this up more, but this is the general idea:
if (_isStickersViewOpen) {
self.tableView.contentInset = UIEdgeInsetsZero;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.center.y);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height + self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = NO;
}];
} else {
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.containerView.frame.size.height, 0.0, 0.0, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height / 2 - self.containerView.bounds.size.height);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height - self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = YES;
}];
}
I'm not claiming this is the most effective or clean solution available, but it works for my purpose.

Resizing UIViews on click of a button using Autolayout

I am new to Auto layout constraints. I have 2 views(topView and paintView) on my main view, along with a button on the top right corner of the main view. On loading the view, the topView occupies the whole main view(excluding the button). On click of the button, I want the topView to occupy 70% of the main view and the paintView to occupy the rest(excluding the button).
I have set up the the X, Y and top constraints for the topView using storyboard. The paintView and the corresponding constraints have been set up programmatically.
The code I have now is this:
-(void)setupPaintView
{
UIView *pPaintView = [UIView new];
[pPaintView setBackgroundColor:[UIColor yellowColor]];
pPaintView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:pPaintView];
self.paintView = pPaintView;
[self addConstraintsToView];
//[self setTopViewFrame];
}
-(void)addConstraintsToView
{
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.paintView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.topView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.topView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.paintView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *pHeightConstraintTopView = [NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
self.heightconstraintTopView = pHeightConstraintTopView;
[self.view addConstraint:pHeightConstraintTopView];
NSLayoutConstraint *pHeightConstraintPaintView = [NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.0
constant:0.0];
self.heightconstraintPaintView = pHeightConstraintPaintView;
[self.view addConstraint:pHeightConstraintPaintView];
}
On button click the following method gets called:
-(IBAction)detailBtnClick:(id)sender
{
if(self.heightconstraintPaintView.constant == 0)
{
self.heightconstraintTopView.constant = 0.7*self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.frame.size.height;
[self.view setNeedsUpdateConstraints];
}
else
{
self.heightconstraintTopView.constant = self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0;
[self.view setNeedsUpdateConstraints];
}
}
When the view loads, the topView acquires the main view's height, which is desired here. But when I click on the button, the topView remains at 100% i.e. it does not resize and neither does the paintView. I am modifying the constant property of the topView and the paintView constraints, but I am not sure that is the correct way to go about it. The constraint here is that the views have to be laid out using Autolayout constraints only. How can I get the views to resize at the click of the button?
Any help is welcome.
Thanks to timothykc and others, I have successfully navigated the problem stated above. But I am facing another issue now.When I change the orientation of the simulator to landscape, the paintView remains almost hidden. Following is the code (toggle is a boolean value that decides whether to stretch/shrink the views):
-(IBAction)detailBtnClick:(id)sender
{
if(self.toggle == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.bounds.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.bounds.frame.size.height;
//[self.view layoutIfNeeded];
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
//[self.view layoutIfNeeded];
}
self.toggle = !self.toggle;
}
The topViewHeightConstraint has been added as a property as indicated by timothykc. This is working properly for the portrait orientation, but is not working properly for landscape, as the height of the topView does not change as desired(70%), meaning that the ratios are not getting handled properly.
I'm going to provide a storyboard driven solution that should help you with other autolayout problems down the road.
My solution to your specific problem, you've got two views (1 and 2 in diagram below):
For view 1, pin the view to the left, top, and right of the superview. Then set a height constant. (e.g. 568, the full height of an iphone 5s)
For view 2, pin it to the left, bottom, and right of the superview. Then pin it to the bottom of view 1.
Open up the assistant editor view, and here's the key trick--turn the height constraint on view 1 into a nslayoutconstraint property on your VC. You do this by locating the constraint, and then control-dragging onto the VC. (e.g.`
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewHeight;`
Now you can manipulate this property with an action linked to your button, such as
- (IBAction)scale:(id)sender {
self.viewHeight.constant = 397.6; //%70 of 568....
}
In my example, I change the nslayoutconstraint.CONSTANT manually to an arbitrary value.
To understand what's happening, you need to know that autolayout is a means for determining the (x coord,y coord,width, height) of any layout object. Warnings occur when xcode cannot ascertain all 4 values...
In View 1, we give a constraint for Height. X,Y, and Width are extrapolated from the distance to the superview. (if something is 0 from the left and right, then the width fills the whole screen...; if 0 from top and left, then coords must be (0,0))
In view 2, X must be 0 since distance from left is 0. width whole screen... Height and Y are extrapolated based on the height of View 1!
So when we mess with height constraint in View 1, it effects the height and Y coord of View 2!
To get constraints to update on a view you would need to call [self.view layoutIfNeeded]; instead of [self.view setNeedsUpdateConstraints]; after setting the new constant on whichever constraint(s) you would like to update.
Actually this is more of an comment about my methods, but I decided to post it as an answer because firstly, this has solved my problem and secondly, it involves some snippets of code which is hard to read in the comments section. Regarding the orientation problem mentioned in the edit, I came up with a workaround to accommodate the view reszing requirements with respect to the toggle button and with respect to orientation change. The three methods used for this purpose are:
The following method is called on the button click event.
-(IBAction)detailBtnClick:(id)sender
{
[self updateViewConstraints:self.toggle];
self.toggle = !self.toggle;
}
The following method updates the constraints.
-(void)updateViewConstraints :(BOOL)toggleValue
{
if(toggleValue == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.bounds.size.height;
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
}
}
The following method calls the method above to update constraints in case of orientation change:
-(void)viewWillLayoutSubviews
{
[self updateViewConstraints:!self.toggle];
}

Autolayout - adding and running constraint

I have a view with 2 container views: one main one on top and one at the bottom.
When the app launches, the bottom one is hidden via a frame that goes beyond the screen height. The top one in the meantime occupies the entire app window.
When I decide to show that bottom container, I want the top container to decrease in height and the view of the controller in that main container to be impacted as well.
I tried to add a constraint programmatically and used layoutIfNeeded but nothing worked.
I'm new to this. I don't necessarily want the best answer but how I should approach this.
Thanks!!!!
-(void)showBottom {
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.bottomContainer attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.mainContainer attribute:NSLayoutAttributeTop multiplier:1.0f constant:49.0f];
[self.view addConstraint:constraint];
}
You can try pinning objects with a Top Space to Superview constraint and animating it.
// .h
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
// .m
[UIView animateWithDuration:1.0 animations:^{
self.topConstraint.constant = 0;
[self.nView layoutIfNeeded];
}];

NSLayoutConstraint between Navigation Bar & ViewControllers View

Can we add a NSLayoutConstraint between self.navigationcontroller.navigationbar and a view inside the self.view. Here self is a UIViewController instance and _textField is a subview of self.view
What I need is that the UI should look alike irrespective whether the navigationBar is Translucent or not.
I've tried the following. But It does not work.
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.navigationController.navigationBar attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.navigationcontroller.view addConstraint:cn];
Yes you can add a constraint between the Navigation Bar and a view. Your root view conroller added to the navigation controller contains topLayoutGuide. so adjust your code like this:
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:_textField
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self.rootViewController.topLayoutGuide attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:20];
[self.rootViewController.view addConstraint:cn];
notice that i'm not referencing the navigation controller at all but the rootViewController of the navigation Controller.
Also you can use bottomLayoutGuide to go above the TabBar the same way. (however if you need to do that you'll run into a bug in iOS frameworks with a workaround patch here: UIViews ending up beneath tab bar )
Check out the topLayoutGuide property on UIViewController.
There's an example in Apple's doc for `UIViewController' that goes like this...
topLayoutGuide
Indicates the highest vertical extent for your onscreen content, for use with Auto Layout constraints. (read-only)
#property(nonatomic, readonly, retain) id<UILayoutSupport> topLayoutGuide
And then...
As an example of how to programmatically use this property with Auto
Layout, say you want to position a control such that its top edge is
20 points below the top layout guide. This scenario applies to any of
the scenarios listed above. Use code similar to the following:
[button setTranslatesAutoresizingMaskIntoConstraints: NO];
id topGuide = myViewController.topLayoutGuide;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings (button, topGuide);
[myViewController.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat: #"V: [topGuide]-20-[button]"
options: 0
metrics: nil
views: viewsDictionary]
self.view layoutSubviews; // You must call this method here or the system raises an exception
];
Add the constraint between the top of the textField and the top of the parent view. The constant for the constraint can be set to the height of the status bar + height of the navigation bar.
Obviously, the following code snippet will only work if both the Status Bar and Navigation Bar are translucent and the view controller wants full screen layout. You can easily test for transparency and adjust accordingly, if necessary.
If you're using interface builder, you can also create an IBOutlet for the existing constraint and just set it's constant rather than creating a new constraint.
// Obtain the view rect of the status bar frame in either portrait or landscape
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect statusBarWindowRect = [self.view.window convertRect:statusBarFrame fromWindow: nil];
CGRect statusBarViewRect = [self.view convertRect:statusBarWindowRect fromView: nil];
// Add Status Bar and Navigation Bar heights together
CGFloat height = self.navigationController.navigationBar.frame.size.height +
statusBarViewRect.size.height;
// Create & Add Constraint
NSLayoutConstraint *constraint =
[NSLayoutConstraint constraintWithItem:self.fieldLabel
attribute:NSLayoutAttributeTop
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:height];
[self.view addConstraint:constraint];

Resources