I have some Autolayout Cells - but would like that they have a minimum height, if the second Label does not contain any text (for example in an form)
I thought the easiest way should be to create an additional Constraint with Priority 999 to setup an minimum height, like in that pic:
But then, the label ("Mayer Thomas") is not self-sizing anymore.
Whats the best way to solve such things? I could create 2 layouts, but in my opinion that should be not a good solution.
Set the height constraint of the optional label as greater than or equal to whatever height you want. Also, set the compression resistance and content hugging of the label to required. Your label should now consume extra height if there is more content, or just take the minimum size that was set.
[label addConstraint:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:15/*the min height you need*/]];
[label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
For that You need to use EqualHeights and change the multiplier accordingly they will self-Size themselves .
To do that just CTRL+Drag the label onto the cell gives equal heights and change the multiplier it will be 1 change it to whatever value you want. Eg: - 0.5 means it will take half the height of the cell.
Related
I'm trying to align two to three buttons horizontally in a view. For simplicity, I'll show my attempt of aligning two buttons.
This works for buttons that have a short title:
#"H:|-10-[questionButton1(questionButton2)]-5-[questionButton2]-10-|"
But as soon as one of the buttons gets a bit longer title, it breaks like this:
What I ended up doing is calculating width of each button and then if button1 width is greater than half of the view and greater than button2 width, I've used:
#"H:|-10-[questionButton1(==btn1width)]-5-[questionButton2(>=btn2width)]-10-|"
It kind of works but I don't really like the look of my code with this kind of calculations. Just imagine complexity it adds with the third button. Also, there is a problem if both buttons have pretty long title in which case I would have to figure out if I should reduce the font size to make everything fit.
I'm posting this here because I might be missing some magical thing regarding autolayout since I only started using it in code today. Any kind of help would be greatly appreciated.
--- UPDATE (clarification) ---
I want the buttons to split evenly considering the margins (10 on the outside and 5 between buttons). Ideally they should be the same width if the text size would fit their default size (50%:50% for two buttons and 33%:33%:33% for three buttons). In case the button title exceeds that perfect width, the button should extend its width if it is allowed by other buttons (if others can shrink). If there is no extension or shrinking possible, the big button should reduce font size and repeat the procedure (check if other buttons can shrink). Yeah, I know, I'm asking for a lot :)
--- UPDATE ---
#Sikhapol's answer helped me solve it. I've added a few things to reduce font size, add padding and make button titles go into multiple lines if the text doesn't fit:
btn.contentEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 5);
btn.titleLabel.adjustsFontSizeToFitWidth = YES;
btn.titleLabel.numberOfLines = 0;
btn.titleLabel.minimumScaleFactor = 0.7;
End result:
Use Content Compression Resistance Priority!
You can tell auto layout to try to maintain the equal width of the two labels as best as it can. But you tell it that it's more important to let one of them grow bigger to fit the content inside.
To do this, set priority of the equal width constraint to be lower than the content compression resistance priority of the labels (or buttons).
- (void)viewDidLoad {
[super viewDidLoad];
UILabel *label1 = [[UILabel alloc] init];
label1.text = #"this seems";
label1.backgroundColor = [UIColor orangeColor];
label1.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *label2 = [[UILabel alloc] init];
label2.text = #"completely fine";
label2.backgroundColor = [UIColor orangeColor];
label2.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:label1];
[self.view addSubview:label2];
NSDictionary *views = NSDictionaryOfVariableBindings(label1, label2);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[label1(label2)]-5-[label2]-10-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil
views:views];
// Find the equal width constraint and set priority to high (750)
for (NSLayoutConstraint *constraint in horizontalConstraints) {
if (constraint.firstAttribute == NSLayoutAttributeWidth) {
constraint.priority = UILayoutPriorityDefaultHigh;
}
}
[self.view addConstraints:horizontalConstraints];
// Set content compression resistant to required (1000)
[label1 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[label2 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// The below code is here to add the vertical center constraints. You can ignore it.
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
}
So if the content can fit inside those labels:
But if one of them grow longer:
Content Compression Resistance Priority is a way to tell the auto layout that how far you want the component to maintain it's intrinsic size (thus the name compression resistance).
This approach can also be achieved more easily in the IB. The content resistance priority can be set in the Size Inspector tab (cmd + opt + 5).
If you're using Auto Layout, you can simply use a constraint to ensure that your buttons are always aligned, either vertically or horizontally. In order to align them horizontally (ie align their y values to be the same), simply select the two buttons by holding command and clicking on them individually:
They will appear in Storyboard with selector indicators around them. Now go to the bottom right corner and choose to align their "Vertical Centers". Aligning their vertical centers will align them horizontally (based on your diagramming).
This ensures that they will always be aligned horizontally.
To fix your problem about the text expansion, one way off the top of my head I can think of to get around that is to create a UIView and then putting a UILabel inside to simulate a button. You would have to link up to the view to some IBOutlet to get when it pressed and link that to the function you want it to perform. But UILabel has attributes you can set in Storyboard shown here with the Attributes Inspector:
If you choose "Minimum Font Size", set that value, then your text will shrink automatically as it fills up the allotted space as seen here:
As the text grows to fill its width, you end up with a constraint ambiguity. There's no telling what will happen! You need to use constraint priorities and inequalities (and perhaps altered compression resistance) to resolve this.
Here's code where I disambiguate between two labels so that one can grow at the expense of the other:
let p = self.lab2.contentCompressionResistancePriorityForAxis(.Horizontal)
self.lab1.setContentCompressionResistancePriority(p+1, forAxis: .Horizontal)
But I also needed to use inequalities to set the widths and spacing originally:
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:[v1(>=20)]-(>=20)-[v2(>=20)]", options: nil, metrics: nil, views: d)
)
I have a UILabel which should be centered horizontally and the width should be set according to its content length. and on the left side of the UILabel an UIImage should be positioned which should be aligned to UILabel. if UILabel needs more space then it should push UIImage to the left, and if UILabel needs less space then it should pull UIImage toward x-center.
I had it without layout working fine, but has to use auto layout. I'm trying but i can't figured it out.
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-padding-[img(16)][lblUserName]-padding-|" options:0 metrics:#{#"padding":[NSNumber numberWithFloat:Padding]} views:displayViewDic];
is it possible with auto layout? so sometimes it will be like in number 1 and other times like number 2.
#"H:|-padding-[img(16)][lblUserName]-padding-|"
Here you're saying that the image has to be a fixed distance from the superview's leading edge. That doesn't match your description.
You might just need to change it to
#"H:|-(>=padding)-[img(16)][lblUserName]-(>=padding)-|"
To allow some flexibility in the margins.
To center a view horizontally, you have to manually create the constraint:
[view addConstraint:[NSLayoutConstraint constraintWithItem:lblUserName
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0]];
You don't need to use sizeToFit or any other methods like that - an image view and a label will have an intrinsic content size based on the image or the text.
Because you have an inequality, you may need to force the label to be as narrow as possible to prevent stretching:
[lblUserName setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
Add a horizontal center constraint to the label. Just this, and a suitable y position constraint would keep the label in the center. It'd expand equally in both directions to accommodate the content.
Now, add a horizontal spacing constraint to the image view's trailing space and the label's leading space for the x position, a suitable constraint for the y position (align vertical center with the label, perhaps?) and suitable constraints/image/intrinsic size for the size.
Code:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[img(width)]-padding-[lblUserName]" options:0 metrics:#{#"width": 50, #"padding": 20} views:displayViewDic]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:lblUserName attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:superview attribute:NSLayoutAttributeCenterX multiplier:0.0 constant:0.0]];
So I'm trying to use autolayout for cell content view to get the proper layout. So my problem is that I have a UILabel that changes its size with respect to its text and I also have a UIView as a background view for this label with rounded corners. So my question is, how to force this UIView's width to be 10 points wider than the UILabel. I managed to make it the same width but how can I force it always to be a certain length wider?
Thank you in advance!
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:yourLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:yourLabel.superview
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:10]; // <-- this
[yourLabel.superview addConstraint:widthConstraint];
An autolayout constraint is nothing but an equation of the form
attribute1 == multiplier × attribute2 + constant
Note that programatically, you can virtually set any constraint on your views. The interface builder is however a bit limited given that you can relate only certain pairs of (attribute1,attribute2) an as you have noticed you may not be able to provide constant.
Have a look at
https://developer.apple.com/library/ios/DOCUMENTATION/AppKit/Reference/NSLayoutConstraint_Class/NSLayoutConstraint/NSLayoutConstraint.html
I have a UIView that has three UILabels on it. I have a title, subtitle, and subtitleDescription, all UILabel properties. I want my title on the top left, the subtitle below the title no gap, and the subtitleDescription to go to the right of the subtitle no gap, aligning the baseline with the subtitle baseline. I want elipses if the view, or views in the case of the subtitle/subtitleDescription. I Would like to use auto layout programmatically.
Similar to this:
_________________________________
|[title] |
|[subtitle][subtitleDescription] |
|________________________________|
I want the labels to go to the upper left hand side rather than centering. In my code right now, it is all centered and all the labels are on top of each other.
I just call sizeToFit on all the UILabel's, other than that I don't adjust the frame at all. Of course this code is after I alloc and init the labels and set the text. Here is my code:
- (void)setup
{
[self.title sizeToFit];
[self.subtitle sizeToFit];
[self.subtitleDescription sizeToFit];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[title]-(>=0)-|"
options:kNilOptions
metrics:nil
views:#{ #"title" : self.title }]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[subtitle]-5-[subdesc]-(>=0)-|"
options:NSLayoutFormatDirectionLeftToRight
metrics:nil
views:#{ #"subtitle" : self.subtitle,
#"subdesc" : self.subtitleDescription }]];
// compR > compR
[self setContentCompressionResistancePriority:900 forAxis:UILayoutConstraintAxisHorizontal];
[self setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[title]-5-[subtitle]|"
options:kNilOptions
metrics:nil
views:#{ #"title" : self.title,
#"subtitle" : self.subtitle }]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.subtitleDescription
attribute:NSLayoutAttributeBaseline
relatedBy:NSLayoutRelationEqual
toItem:self.subtitle
attribute:NSLayoutAttributeBaseline
multiplier:1.0
constant:0]];
}
Thank you!!!
Here is a screen shot of the view:
Update
As jrturton pointed out to me, it looks like all my constraints are being broken from the constraints exceptions. I am looking to figure out why they are broken. The message given is "Unable to simultaneously satisfy constraints".
First of all, make sure your labels are autolayout-enabled by setting translatesAutoResizingMaskIntoConstraints = NO.
You don't need any of the sizeToFit calls. At the point of adding constraints, they are meaningless. The labels will use their intrinsic size at the point of layout.
To prevent centring, simply don't pin to both sides of the superview. So instead of this:
"|[title]-(>=0)-|"
Do this:
"|[title]"
Or indeed this:
"|[title]|"
And set left alignment on the label.
For multiple labels in a line, you'd want this:
"|[subtitle]-5-[subdesc]|"
Passing NSLayoutFormatAlignAllBaseline in the options. You can OR (|) the options together if required. Again, you don't need the inequality spacing. A left aligned label will take as much space as it needs to. You may want to set compression resistance / hugging priorities on the two labels so you have rules on which one is truncated if there isn't enough room to display both values.
You're setting content compression resistance on the view itself, this is meaningless if the view itself is not also subject to auto layout. You're also setting it twice to two different values, I'm not sure what you are hoping to achieve with that.
Your current vertical constraints will, if the superview has a fixed size, cause one or other of the labels to be stretched to fill the remaining size, which will centre the text vertically, that's what labels do when they are too tall.
You can overcome this by not pinning to the bottom:
"V:[title]-5-[subtitle]"
I've written extensively about VFL and auto layout here, with links to other autolayout-related articles.
I'm trying to use programmatic visual constraints to display a label and a button next to one another. However, the UIImageView used as the button's background is making the intrinsic size of the button much too large.
I attempted to add a constraint that forces the height of the button to match the height of the label. But I just got a super tall label instead of a smaller button.
How do I set a constraint so that the button height is the same height as the label (and not vice-versa)
The button should keep the original aspect ratio of the image - its width should also match its own height (maybe this comes for free?)
The following works for this:
Set a width for the button in the visual layout: #"|-[titleLabel][refreshButton(==26)]"
Add a constraint such that the height of the button is equal to its own (now explicit) width:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:refreshButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:refreshButton
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f];
I would still prefer a solution that uses the label's height, instead of a fixed value.