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];
Related
Description:
the center view belongs to a view controller. I added a the center view to a view controller, the codes as follows:
FNHAHChooseProjectController *chVC = [FNHAHChooseProjectController new];
chVC.title = #"选择项目";
self.containViewController = [[UINavigationController alloc]initWithRootViewController:chVC];
_containView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, FNDeviceWidth*0.8, FNDeviceHeight*0.5)];
_containView.cornerRadius = 10;
[_containBgView addSubview:_containView];
_containView.center = CGPointMake(_containBgView.width*0.5, _containBgView.height*0.5);
[self addChildViewController:self.containViewController];
self.containViewController.view.frame = self.containView.bounds;
self.containViewController.view.autoresizingMask =UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
[self.containView addSubview:self.containViewController.view];
[self.containViewController didMoveToParentViewController:self];
when I clicked the first cell to push a new view controller, the table view automatically made a contentOffset.I think the contentOffset's height is about 20 p,the statusBar's height.I don't know why it caused;
The UI effect is just like the gif as follows:
Select your viewController and uncheck the Adjust ScrollView Insets as shown below :)
This should solve your problem :)
The 20 points is the topLayoutGuide of the UIViewController, you need to set the constraint of your content align to the topLayoutGuide of the UIViewController.
[NSLayoutConstraint constraintWithItem: YourContents
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:20.0];
With iOS 9 you can also create the NSLayoutConstraint this way:
[self.button.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor
constant:20.0];
self.automaticallyAdjustsScrollViewInsets = NO;
Here is my problem, I have a scroll view scrollExerciseIndex that I use only as a scrolling bar, in this scroll view I place a UIView indexesView and I want it to be always at the center of the scroll view. For this I use layout constraints :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
[self.scrollExerciseIndex addSubview:indexesView];
[self.scrollExerciseIndex setContentSize:CGSizeMake(dimension*numberIndexes, dimension)];
if (xPosition != 0) {
NSLayoutConstraint * xCenterConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.scrollExerciseIndex attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
[self.scrollExerciseIndex addConstraint:xCenterConstraint];
}
Here is the expected result :
Don't pay attention to all the element, just the bar at the bottom of the screen is my problem.
I have to create view programmatically because sometimes I will activate the constraints, sometimes not and I have to set the frame of the view dynamically. So for now I initialise the view indexesView like so :
UIView * indexesView = [[UIView alloc] initWithFrame: CGRectMake(xPosition, 0, dimension*numberIndexes, dimension)];
(I know, not very original)
I would like to know if there is a way to initialize the view programmatically but to say to auto-layout that it has no constraints on the position because right now if the screen turns in landscape mode there is a conflict as the scrollview's frame changes so the distance between the center of the scroll view (on which I set a constraint) and the position of the subview's frame (xPosition) is no longer the same.
As you can see, the view is no longer at the center of the scroll view and I have some constraints broken.
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x7bed6c50 UIView:0x7bed6ad0.centerX == UIScrollView:0x7e273200.centerX
Thanks for your help.
Ok, I found what I was looking for by reading a book about Audio-Layout.
My problem was that audio layout would create constraints behind my back automatically. When using AutoLayout a type of constraints is created from non-autoLayout specifications (The used to describe interface when auto layout didn't exist). So constraints are created using the initial frame of the view. The only thing I had to do was :
[indexesView setTranslatesAutoresizingMaskIntoConstraints:NO];
to disable this creation of constraints from the frame, and then recreate explicitly the constraints for width and height if needed (which wasn't the case for me, but I still made the test) like so :
`NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:widthValue];
NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:indexesView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:heightValue];
[indexesView addConstraint: heightConstraint];
[indexesView addConstraint: widthConstraint];`
When adding constraints programmatically, don't forget to call : [indexesView setNeedsUpdateConstraints]; so the constraints are recalculated only when needed.
Last info that I read and can be useful in general, when adding a lot of constraints, the apple doc specifies that it is more efficient to use the method :
[myView addConstraints:(NSArray<NSLayoutConstraints *> *)] than to call addConstraint: for each constraint.
Hope it can be useful to someone.
My main scene shows a child UIViewController, by putting a Child Container in its View, and embedding a child UIViewController in that Child Container. I do that by ctrl-dragging from the Child Container to the child UIViewController in the storyboard, and choosing the "embed" segue.
That lets me encapsulate and reuse the child UIViewController elsewhere. I think that is fairly standard. But I can't get it to auto layout properly.
I've made a simple test case to demonstrate this:
https://github.com/murraycu/ios-example-autolayout-of-child-container-views
It has two UIViewControllers that are embedded in a Tab Controller so you can switch between them.
The "Simple" tab shows the SimpleViewController, which shows an image and a label, seen here in the storyboard:
Neither the UIImage or the UILabel has a height constraint specified, though they have an Equal Width (equal to the parent view) constraint to keep the widths simple.
That UILabel is clearly not very high but the UIImage and UILabel have slightly different Content Hugging priorities, making their heights not ambiguous according to Auto Layout. So thanks to auto layout, when I set its text at runtime to some text that would require more space, it takes up more space, taking space away from the UIImage above it. That's good - it is the behaviour that I want, as seen in the emulator:
Now to the problem: The "With Child Container" tab shows the WithChildContainerViewController, which shows the same image but shows my ChildViewController (embedded in a Child Container) instead of the UILabel. And that embedded ChildViewController shows the UILabel. Here it is in the storyboard:
However, the auto layout system now doesn't seem to know how much space the Child Container needs to show all the text in the label in my ChildViewController. As in the "Simple" tab, neither the UIImage or the Child Container has a height constraint specified. Now XCode complains that "Height is ambiguous for "container view". And it looks like this in the simulator:
That's improved if I add a constraint to the Child Container, constraining its bottom to the bottom of the parent view, as suggested by #iphonic: https://github.com/murraycu/ios-example-autolayout-of-child-container-views/commit/1d295fe0a6c4502764f8725d3b99adf8dab6b9ae but the height is still wrong:
How can I let the auto layout system know what to do? I've thought about implementing UIView's intrinsicContentSize, but UIViewController isn't a UIView.
Suggestion is, do it programatically rather than via IB. See below
_childController=[self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:_childController];
[_container addSubview:_childController.view];
[_childController didMoveToParentViewController:self];
_childController.view.frame=_container.bounds;
//add constraints
UIView *subView=_childController.view;
UIView *parent=_container;
subView.translatesAutoresizingMaskIntoConstraints=NO;
NSLayoutConstraint *width =[NSLayoutConstraint
constraintWithItem:subView
attribute:NSLayoutAttributeWidth
relatedBy:0
toItem:parent
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
NSLayoutConstraint *height =[NSLayoutConstraint
constraintWithItem:subView
attribute:NSLayoutAttributeHeight
relatedBy:0
toItem:parent
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
NSLayoutConstraint *top = [NSLayoutConstraint
constraintWithItem:subView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:parent
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.f];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:subView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:parent
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
[parent addConstraint:width];
[parent addConstraint:height];
[parent addConstraint:top];
[parent addConstraint:leading];
Hope it helps.
Cheers.
#iphonic's code but using Visual Format Language
_childController = [self.storyboard instantiateViewControllerWithIdentifier: #"ChildController"];
[self addChildViewController: _childController];
[_container addSubview: _childController.view];
[_childController didMoveToParentViewController: self];
//add constraints
NSDictionary *views = #{#"subView": _childController.view,
#"parent": _container};
[parent addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat: #"V:|[subView(==parent)]"
options: 0
metrics: nil
views: views]];
[parent addConstraints: [NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[subView(==parent)]"
options: 0
metrics: nil
views: views]];
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];
}
When I align a UILabel to the top, my option is superview but it always uses the nav bar as the reference point. This causes problems when I hide the navigation bar and the constraint adjusts to the fact that there's no longer a nav bar and it causes the label to be pulled higher, which is an unfortunate reaction I don't want to happen (I want it to stay in place).
Is it possible to just say my UILabel should be always, say, 100pt from the top of the full screen view?
I'm not sure about Interface Builder, but you can do this easily in code, e.g.:
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-100-[label]"
options:0
metrics:nil
views:#{#"label" : self.label}]];
UPDATE: Previously I've assumed that the label is a subview of the main view. If it isn't then you can use the following constraint:
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.label
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:100]];
I've tested it and it's working fine.