I have a UITableViewCell that has two columns in it. Each column is a UILabel and each label is multiline (numberOfLines = 0).
What I would like is to have the table view cell vertically sized based on whichever label is taller. I have constraints setup for the left right and top of each label, but I am not sure how to add the bottom constraints since it needs to be a constraint on the tallest label.
Here is what I have right now:
But this is what I am trying to achieve. But either the left or the right column could be taller. In the picture, the right column is taller, but it just as well could be the left column depending on the data supplied to it.
I have thought about adding a height constraint to constraint both labels to the same height and then adding the bottom constraint from there, but then the shorter label will not be vertically aligned, or I do not know of a way of aligning them vertically without subclassing UILabel or using UITextView, which I would prefer to not do if possible.
Is there a good way to have the table view cell be able to vertically auto-size based on whichever column is taller? Thanks for your help.
Update
I have added two additional constraints based on the answer provided. But for some reason I still cannot get it to autosize the table cell. I have the row height set to automatic in Interface Builder. Here are the constraints I have configured currently.
Is there anything in the constraints that would prevent the table view cell from increasing in height to match the height of the labels?
I am not sure if this is the problem or not, but I tried to add a low priority height constraint to the content view also, as was suggested, but I am unable to add the constraint or I do not know how to do that. I can add a height constraint to other views, but not to the content view of the table view cell.
Update 2
Here are the constraints in code. This is in a UITableViewCell subclass and this code runs as part of the initialization of the cell.
[self addSubview:self.firstLabel];
[self addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor constant:0.0f],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.firstBaselineAnchor constraintEqualToAnchor:self.firstLabel.firstBaselineAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.heightAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.heightAnchor constant:8.0f],
[self.contentView.heightAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.heightAnchor constant:8.0f],
heightConstraint
]];
Here is what it looks like when run on the device. The labels are all short, except the first one, which is supposed to span several lines. But for some reason, it is being truncated even though I have the number of lines set to 0 and I think the content hugging and content compression resistance priorities set to what I think should be correct.
Here are how my labels are defined:
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
//TODO: remove this
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
//TODO: remove this
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
Yes, it's pretty straightforward when you understand about constraint inequalities and priorities. Here, the label on the left is longer:
Here, the label on the right is longer:
The yellow view, which stands in for a table view cell here, is sized to the larger of the labels.
How is that done? In addition to the labels being pinned top, left, and right in the normal way, the superview (yellow view) bottom has two greater-than-or-equal constraints, one to the bottom of each label; and it is itself given a very small height at a very low priority (as a way of telling the layout engine to make the height as small as possible while still obeying the inequalities, which would otherwise be ambiguous).
EDIT There appears to be some doubt that this would work for an actual table view, so here's proof that it does.
Related
I know a lot of questions have been asked about it but could not figure out.
I have a label that needs its number of lines adusting dynamically. It works with automatic preferred max width, but could not make it work when explicit (I want to support ios7).
The label height does not currently increase
Here is my code:
-(void)viewDidLoad
{
[super viewDidLoad];
_feedBackLabel.numberOfLines = 0;
_feedBackLabel.lineBreakMode = NSLineBreakByWordWrapping;
_feedBackLabel.preferredMaxLayoutWidth = self.view.frame.size.width - 90;
[_feedBackLabel setContentHuggingPriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisVertical];
[_feedBackLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
}
-(void)setFeedback:(NSString*)response
{
self.feedBackLabel.text = response;
self.feedBackLabel.hidden = NO;
[self.feedBackLabel sizeToFit];
}
I have pinned my label with a leading and a trailing space to its superview with priority required.
The content hugging and compression resistance are both 750 in the vertical axis.
The label has a height constraint with a priority of 500.
Thanks
Guillaume
Remove height constraint from label and run your code
Actually it was working partially. It was only working for long strings; if the string was just a bit too big for my label then it did not work.
What I did is increase my horizontal content hugging priority to 750, i.e. higher than the height priority which is 500 (all other constraints are required), and set the preferredMaxLayoutWidth dynamically, and it works as expected now.
Thanks
Guillaume
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)
)
Xcode Interface Builder issue
Personally I do not like the way that interface builder works in Xcode. In this example I am trying to create a fairly complex view controller. On the viewDidLoad of the view controller I show a custom alert view (as such). It is not actually an alert view but more of a view that shows the user some information. I have a dimmed background view and a view on top of this. If I try to create this in interface builder it gets overly complicated as you cannot select the views in the background and move them etc without dropping subviews into the wrong views and so on...
Scenario
What I am trying to do is create a View which holds some labels and a button. The view controller has a difficulty property based on this it will have different text in the labels/amount of labels.
I.e. Easy -- 3 labels
Hard -- 4 labels
I create the dimmedView and alert(styled)View like this:
// Setup the dimmedView
UIView *dimmedView = [[UIView alloc] initWithFrame:self.view.frame];
dimmedView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6];
// Setup the startingAlertView
UIView *startingAlertView = [[UIView alloc] init];
startingAlertView.backgroundColor = [UIColor whiteColor];
I then create the three/four labels based on some logic and add the necassary labels to the startingAlertView based on logic also.
The issue that is obvious is that at no point a frame for the view is set. This means that it is returning 0,0,0,0. What I would like to happen is the view to take the required height based on the labels added.
I am building for IOS7 and using Auto Layout. Should I be setting up constraints which would then adjust the relevant heights and locations in the view possibly?
I am building for IOS7 and using Auto Layout. Should I be setting up constraints which would then adjust the relevant heights and locations in the view possibly?
Yes. you don't use initWithFrame: under auto layout, or rather, you can, but the frame is ignored. Create your dimming view with a frame of CGRectZero, setting translatesAutoresizingMasksToConstraints to NO, add it to your main view and create constraints pinning it to all edges of the superview.
Then, add your alert view, again with a frame of zero and the translates... property set to NO. Create constraints to centre this view in your dimming view. This view will get its size from its subviews, since labels have an intrinsic size.
Add your labels as subviews of this view, with frame of zero and translates... set to NO. Depending on their content you may wish to set preferred max layout width or a width constraint.
Create constraints pinning your labels to the left and right edges of the superview, and lining your labels up in a vertical 'stack'. In each case you could add padding to give your alert a bit of a border.
This can look like a large amount of code, so you may want to read the articles I've written on visual format for auto layout and creating constraints in code, with the associated autolayout convenience category to make your life easier.
If you're going to the auto layout route, then you can add constraints that will keep the proper space between each label, and the proper space between the top and bottom of the view with the first and last labels. However, if you're not doing this in Interface Builder, you might as well skip using auto layout also, because it's fairly simple to just adjust the height of the view as you add labels.
You would start by setting the height of the view to the size of the top and bottom spaces that you want to have around the labels. Then each time you add a label, add to it the height of the label plus the height of the space you're putting between labels.
You could also wait until you've added all of the labels that you want, then set the height to the bottom label's y position plus its height plus the bottom space you want to have around the labels.
Yes, using autolayout you can get the bounds from the parent view.
Here is a quick example, notice that we are not using frame, and using CGRectZero for our UILabels, the positioning comes from updateConstraints instead. I am using Visual Format Language to layout the labels which I recommend if you are doing it programatically.
Here we are making the labels the width of the parent view and then just stacked on top of each other.
#import "View.h"
#implementation View{
UILabel *_label1;
UILabel *_label2;
UILabel *_label3;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_label1 = [[UILabel alloc] initWithFrame:CGRectZero];
_label1.translatesAutoresizingMaskIntoConstraints = NO;
_label1.text = #"LABEL 1";
_label2 = [[UILabel alloc] initWithFrame:CGRectZero];
_label2.translatesAutoresizingMaskIntoConstraints = NO;
_label2.text = #"LABEL 2";
_label3 = [[UILabel alloc] initWithFrame:CGRectZero];
_label3.translatesAutoresizingMaskIntoConstraints = NO;
_label3.text = #"LABEL 3";
[self addSubview:_label1];
[self addSubview:_label2];
[self addSubview:_label3];
}
[self updateConstraintsIfNeeded];
return self;
}
-(void)updateConstraints
{
[super updateConstraints];
NSDictionary *_viewsDictionary = NSDictionaryOfVariableBindings(_label1,_label2,_label3);
// Set the contraintsto span the entire width of the super view
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_label1]-|"
options:0
metrics:nil
views:_viewsDictionary];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_label2]-|"
options:0
metrics:nil
views:_viewsDictionary];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[_label3]-|"
options:0
metrics:nil
views:_viewsDictionary];
[self addConstraints:constraints];
// Last setup the vertical contraints other wise they will end up in a random place
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[_label1]-[_label2]-[_label3]"
options:0
metrics:nil
views:_viewsDictionary];
[self addConstraints:constraints];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I have a view controller with 12 UITextFields.
It fits a 3.5" display very well.
I need to set it for iPhone 5 (4 inch display) such that all UITextFields cover the whole UIView by adding extra space in between.
I am trying to do this by auto layout but it is not working properly.
This is my code :
- (void) viewWillLayoutSubviews
{
int h = txt1.bounds.size.height * 12;
float unusedHorizontalSpace = self.view.bounds.size.height - h ;
NSNumber* spaceBetweenEachButton= [NSNumber numberWithFloat: unusedHorizontalSpace / 13 ] ;
NSMutableArray *constraintsForButtons = [[NSMutableArray alloc] init];
[constraintsForButtons addObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat: #"V:|-50-[txt1(==30)]-(space)-[txt2(==txt1)]-(space)-[txt3(==txt1)]-(space)-[txt4(==txt1)]-(space)-[txt5(==txt1)]-(space)-[txt6(==txt1)]-(space)-[txt7(==txt1)]-(space)-[txt8(==txt1)]-(space)-[txt9(==txt1)]-(space)-[txt10(==txt1)]-(space)-[txt11(==txt1)]-(space)-[txt12]-(space)-|"
options: NSLayoutFormatAlignAllCenterX
metrics: #{#"space":spaceBetweenEachButton}
views: NSDictionaryOfVariableBindings(txt1,txt10,txt11,txt12,txt2,txt3,txt4,txt5,txt6, txt7,txt8,txt9)]];
[self.view addConstraints:constraintsForButtons];
}
If I do [txt12(==txt1)] then it displays the same as the 3.5" screen and leaves space below.
Where I am making a mistake?
To do this with auto layout, you must create extra views to fill the spaces between the text fields.
Recall that an auto layout constraint is basically the linear equation A = m * B + c. A is an attribute of one view (for example, the Y coordinate of viewA's bottom edge) and B is an attribute of another view (for example, the Y coordinate of viewB's top edge). m and c are constants. So, for example, to lay out viewA and viewB so that there are 30 points between the bottom of viewA and the top of viewB, we could create a constraint where m is 1 and c is -30.
The problem you're having is that you want to use the same value for c across 13 different constraints, and you want auto layout to compute that c value for you. Auto layout simply can't do that. Not directly. Auto layout can only compute the attributes of views; it cannot compute the m and c constants.
There is a way to make auto layout put the views where you want: reify the spaces between the text fields as additional (invisible) views. Here's an example with just 3 text fields:
We'll create a constraint to pin each spacer's top edge to the bottom edge of the text field above it. We'll also create a constraint to pin each spacer's bottom edge to the top edge of the text field below it. And finally, we'll create a constraint to force each spacer to have the same height as the topmost spacer.
We'll need a two instance variables to set things up: an array of the text fields (in order from top to bottom), and a reference to the topmost spacer view:
#implementation ViewController {
NSMutableArray *textFields;
UIView *topSpacer;
}
We'll create the text fields and spacers in code since it's hard to show a xib in a stackoverflow answer. We kick things off in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addTextFields];
[self addSpacers];
}
Since we're going to use auto layout, we need to turn off translatesAutoresizingMaskIntoConstraints to prevent the system from creating extra constraints.
We create each text field, give it some dummy text, and set up constraints for its horizontal position and size:
- (void)addTextFields {
textFields = [NSMutableArray array];
for (int i = 0; i < 12; ++i) {
[self addTextField];
}
}
- (void)addTextField {
UITextField *field = [[UITextField alloc] init];
field.backgroundColor = [UIColor colorWithHue:0.8 saturation:0.1 brightness:0.9 alpha:1];
field.translatesAutoresizingMaskIntoConstraints = NO;
field.text = [field description];
[self.view addSubview:field];
[field setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[field setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-[field]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(field)]];
[textFields addObject:field];
}
We'll use a loop to create the spacers too, but we create the top and bottom spacers differently from the middle spacers, because we need to pin the top and bottom spacers to the superview:
- (void)addSpacers {
[self addTopSpacer];
for (int i = 1, count = textFields.count; i < count; ++i) {
[self addSpacerFromBottomOfView:textFields[i - 1]
toTopOfView:textFields[i]];
}
[self addBottomSpacer];
}
Here's how we create the top spacer and set up its constraints. Its top edge is pinned to the superview and its bottom edge is pinned to the first (topmost) text field. We store the top spacer in the instance variable topSpacer so we can constrain the other spacers to have the same height as the top spacer.
- (void)addTopSpacer {
UIView *spacer = [self newSpacer];
UITextField *field = textFields[0];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[spacer][field]" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, field)]];
topSpacer = spacer;
}
Here's how we actually create a spacer view. It's just a hidden view. Since we don't care about its horizontal size or position, we just pin it to the left and right edges of the superview.
- (UIView *)newSpacer {
UIView *spacer = [[UIView alloc] init];
spacer.hidden = YES; // Views participate in layout even when hidden.
spacer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"|[spacer]|" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer)]];
return spacer;
}
To create a “middle” spacer between two text views, we pin it to the bottom edge of the text
field above and the top edge of the text field below. We also constrain its height to equal the height of the top spacer.
- (void)addSpacerFromBottomOfView:(UIView *)overView toTopOfView:(UIView *)underView {
UIView *spacer = [self newSpacer];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[overView][spacer(==topSpacer)][underView]" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, overView, underView, topSpacer)]];
}
To create the bottom spacer, we pin it to the last text field and to the superview. We also constrain its height to equal the height of the top spacer.
- (void)addBottomSpacer {
UIView *spacer = [self newSpacer];
UITextField *field = textFields.lastObject;
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[field][spacer(==topSpacer)]|" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, field, topSpacer)]];
}
If you do it right, you will get a result like this:
You can find a complete example project in this github repository.
Check out PureLayout. It's designed to be the simplest and most programmer-friendly API possible for creating Auto Layout constraints in code.
In response to your specific question, PureLayout offers a two primary APIs for distributing views, one where the spacing between each view is fixed (view size varies as needed), and the other where the size of each view is fixed (spacing between views varies as needed). The latter will accomplish what you're looking for without the use of any "spacer views".
// NSArray+PureLayout.h
// ...
/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
alignedTo:(ALAttribute)alignment
withFixedSpacing:(CGFloat)spacing;
/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
alignedTo:(ALAttribute)alignment
withFixedSize:(CGFloat)size;
// ...
see developer.apple' documentation, that is having nice description about the solution, https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html see the spacing and warping in that page, i think that is nice description, so no need to explain same thing over here
EDIT
Above link is now get disabled by apple, As from iOS 9, they have introduced Stackview, which is solution for all this.
Previously in above link the answer was same as answer provided by #rob
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.