Autolayout Constraints Priority is not Resolving - ios

I have a view which has a UILabel, a UITableView(tblFilters) and a UIView(btnBaseView)(to keep three other UIButtons).Please check the image below: -
I need to expand the tblFilters height to showcase the options for each category but need to have the btnBaseView always visible on the screen. So basically tblFilters height should not increase beyond a limit.
To achieve this i have applied a height constaint to btnBaseView and gave it Required priority. Same way tblFilters has a height constraint but a DefaultHigh priority.
// Height Constraint of btnBaseView. Height Should always be >=116
btnSectionHeightConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[btnBaseView(>=116)]" options:0 metrics:nil views:#{#"btnBaseView":btnBaseView}];
[[btnSectionHeightConstraint firstObject] setPriority:UILayoutPriorityRequired];
[self addConstraints:btnSectionHeightConstraint];
// TableView Height Constraint. Height value is being changed when user click on "+" button of table section.
tableHeightConstraints = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[tblFilters(>=%f)]",176.0] options:0 metrics:nil views:#{#"tblFilters":tblFilters}];
[[tableHeightConstraints firstObject] setPriority:UILayoutPriorityDefaultLow];
[self addConstraints:tableHeightConstraints];
But this scheme doesn't seems to work as tableView is covering the entire baseView and pushed the btnBaseView out of visible area.
I have tried by keeping DefaultLow priority to tblFilters as well but no effect. When i debug the code after changing the tblFilters height constraint it print correct priority output in console but no effect over the view.
Can someone please help me in identifying the issue why constraint priority is not working as expected or do i have wrong understanding of this concept. Any help is appreciated.

set tblFilters height constraint priority to be
UILayoutPriorityDefaultHigh
set btnBaseView top margin constraint related by tblFilters to
0
set btnBaseView bottom margin constraint related by bottomView
to be >= 0
and then you change height constraint of tblFilters corresponding to the data
tableHeightConstraints.constant = someValue
view.layoutIfNeeded()

Related

Superview not increasing in height based on the subviews constraint

I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.

Resize UIView and update content with Autolayout

This is the current setup I'm using. In my storyboard I have a ViewController with a UIScrollVIew that is position and sized with Autolayout.
The content of the scrollView is added programatically based on UIView built in interface builder. It has several labels, all of them using autolayout: leading and trailing to superview, and vertical spaces.
The scrollview gets resized accordingly to the screen size(iPhone / iPad), but the contentView's width is always the one specified in interface builder. I've tried setting contentview's frame width to match the scrollview frame width, and that changes it's frame but the labels width stay exactly the same. Also when I start to scroll it reverts my frame change. Any ideas how I can make the content's width match the scrollview's and update it's content accordingly?
I'm also adding constanints from the contentView to the scrollView programatically:
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollContent]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollContent]|" options:0 metrics: 0 views:viewsDictionary]];
Setting the frame's width didn't work but apparently making a width constraint did the trick. Not sure why but maybe it helps other people as well.

Get same result with NSLayoutConstraint as with Autosizing

In my UI I have 5 buttons at the bottom. With autosizing applied to every button like on the picutre:
I get desired results:
However, when I tried to do it with Autolayout in IB or in code like this:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_button1, _button2, _button3, _button4, _button5);
NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"|-[_button1]-[_button2]-[_button3]-[_button4]-[_button5]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDictionary];
[self.view addConstraints:constraints];
I get this:
Even when I try to set default width, I don't get behaviour I expected.
You need to set the equal width constraint to all the buttons and set the horizontal space constraint between each buttons through IB.
To set equal height
* Select all buttons
* Editor -> Pin -> Width equally
Hope this works well, as it worked for me.
#"|-[_button1]-[_button2(==_button1)]-[_button3(==_button1)]-[_button4(==_button1)]-[_button5(==_button1)]-|"
Gets you all equal width buttons, although they will stretch to fill the space in landscape...

Using constraints to center an item between two elements

I tried, in interface builder, to position an image between a button and the bottom of the view, and have stay centered in different screen sizes. I could not find a way to do this, so I've tried to accomplish that using the code below, but it's not working. I can get it centered using explicit points, but if use >= it hugs the bottom and all the space is added between the image and button.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(image, button);
NSArray *constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button]->=1-[image]->=1-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:viewsDictionary];
for (int i = 0; i<constraintsArray.count; i++) {
[self.view addConstraint:constraintsArray[i]];
}
How can I get it to center?
Unfortunately, you can't use the >= like that, but it can be done easily in IB. Just give the image view a spacing constraint to the bottom of the superview, and a vertical spacing constraint to the button -- edit one or the other of these to have the same value as the other. Give the image view a fixed height and width constraint, and make sure that the button has no other vertical constraints (delete it/them if it does).

Dynamic UILabel with proportional width and text-driven height

I'm trying to programmatically create a container view with two UILabel subviews which behave as follows:
The container width is pinned to its superview; its height is constrained to fit the labels
The labels are laid out horizontally, with standard spacing between them (8pts)
The left label width is 25% of the width of the container
The right label width fills the available space, minus standard horizontal spacing
Long text should be broken at word boundaries are flow across multiple lines; both labels must grow vertically to accommodate long text
I have defined the labels with numberOfLines = 0 and lineBreakMode = NSLineBreakByWordWrapping.
Note that the size of the container is completely dynamic; its width is determined by its superview, while its height is determined by its subviews (the labels). The size of the labels is also dynamic; their widths are proportional to the container width, and their heights depend on the length of the text.
I've been able to achieve everything above, except for the last item, with the following constraints (pseudo-code). A is the left label, B is the right.
A.top == container.top
B.top == container.top
A.leading = container.leading
A.trailing == B.leading - 8
B.trailing == container.trailing
A == .25 * container.width
container.height >= A.height
container.height >= B.height
The last 2 constraints are intended to stretch the container to fit the taller of the labels, but the layout engine seems to ignore the fact that the labels may be multiline. That is, I always get a single line displayed, no matter the length of the text.
So what constraints do I need to add/modify/delete in order to achieve the full set of behaviors described above?
To make your labels automatically resize height you need to do following:
Set layout constrains for label (That what you actually have done)
Set height constraint with low priority. It should be lover than ContentCompressionResistancePriority
Set numberOfLines = 0
Set ContentHuggingPriority higher than label's hight priority
Set preferredMaxLayoutWidth for label. That value is used by label to calculate its height
For example:
self.descriptionLabel = [[[UILabel alloc] init] autorelease];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;
[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];
NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:#"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[descriptionLabel_(220#300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
Set the priority of Height Constraints for the labels to low value and try setting the constraints in code.
Make sure you set
horizontal and vertical content compression resistance priority. If you do not want label to truncate its content then set it to 1000.i.e. required.
Content hugging priority. Look at this answer to understand how it works.

Resources