Using Auto Layout to Have UIlabel and UItextfield Next to Each Other

Using Auto Layout to have UILabel and UITextField next to each other

Sample Image

EDITED: So after the comment of Rahul I played with width and priorities. Adding the following width constraint lead into the somewhat wrong direction, because of entering some very long value, the label was resized too small.

[self.label constrainWidth:@"0@1"];

Sample Image

So I finally read up on "Compression Resistance and Content Hugging" (http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html) and the solution is to set the Horizontal Content Hugging priority of the label to a higher value.

[self.label setContentHuggingPriority:500 forAxis:UILayoutConstraintAxisHorizontal];

Then even entering big values will let the label stay as width as its content!

Solution is not adding a width constraint with priority, but to set Compression Resistance and Content Hugging properties for the intrinsic content size of the UIViews.

Sample Image

UILabel's width is exceeding the text using programmed Auto-Layout

Try set hugging and commpresion resistance priority on your UILabel and UITextField

Sample Image

Using Autolayout to position UITextField next to UILabel

Instead of trying to manipulate Apple's standard cell, I took the plunge and wrote my own UITableViewCell subclass that mimics UITableViewCellStyleValue1. When the tableview goes into edit mode, in the simplest terms I hide the value label and display the textfield. For those who might be struggling with the same thing, I'm posting some code to help you get started:

@interface NXAlphaNumericTextFieldCell : UITableViewCell<UITextFieldDelegate,NumberKeyboardDelegate>

@property (strong, nonatomic) UITextField *inputTextField;
@property (strong, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) UILabel *valueLabel;

@property (strong, nonatomic) NSArray *xTitleLabelConstraints;
@property (strong, nonatomic) NSArray *xTextFieldConstraints;
@property (strong, nonatomic) NSArray *xValueLabelConstraints;

@end

And a few methods in the implementation:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.titleLabel.font = [UIFont boldSystemFontOfSize:16.0f];
self.titleLabel.backgroundColor = [UIColor clearColor];

self.valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
self.valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.valueLabel.textColor = [UIColor colorWithRed:81.0/255.0 green:102.0/255.0 blue:145.0/255.0 alpha:1.0];
self.valueLabel.backgroundColor = [UIColor clearColor];

self.inputTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 44.0f)];
self.inputTextField.translatesAutoresizingMaskIntoConstraints = NO;
self.inputTextField.autocapitalizationType = UITextAutocapitalizationTypeWords;
self.inputTextField.autocorrectionType = UITextAutocorrectionTypeYes;
self.inputTextField.clearButtonMode = UITextFieldViewModeAlways;
self.inputTextField.delegate = self;

[self.contentView addSubview:self.valueLabel];
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.inputTextField];

UILabel *textLabel = self.titleLabel;
NSDictionary *labelTextFieldViewsDictionary = NSDictionaryOfVariableBindings(textLabel);
self.xTitleLabelConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[textLabel]"
options:0
metrics:nil
views:labelTextFieldViewsDictionary];
UITextField *textfield = self.inputTextField;
labelTextFieldViewsDictionary = NSDictionaryOfVariableBindings(textLabel, textfield);
self.xTextFieldConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[textLabel]-50-[textfield]-|"
options:0
metrics:nil
views:labelTextFieldViewsDictionary];
UILabel *valueLabel = self.valueLabel;
labelTextFieldViewsDictionary = NSDictionaryOfVariableBindings(valueLabel);
self.xValueLabelConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[valueLabel]-|"
options:0
metrics:nil
views:labelTextFieldViewsDictionary];

[self setNeedsUpdateConstraints];
}
return self;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];

// Configure the view for the selected state
if (self.isEditing) {
[self.inputTextField becomeFirstResponder];
}
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];

[self addConstraints:self.xTitleLabelConstraints];
if (editing) {
if (self.inputType == kCellInputTypeAlphaNumeric) {
self.inputTextField.keyboardType = UIKeyboardTypeAlphabet;
} else if (self.inputType == kCellInputTypeEmail) {
self.inputTextField.keyboardType = UIKeyboardTypeEmailAddress;
} else if (self.inputType == kCellInputTypePhoneNumber) {
self.inputTextField.keyboardType = UIKeyboardTypeNamePhonePad;
} else {
if (!self.numberKeyboard) {
self.numberKeyboard = [[NumberKeyboard alloc] initWithNibName:@"NumberKeyboard" bundle:nil];
self.numberKeyboard.textField = self.inputTextField;
self.numberKeyboard.showsPeriod = YES;
self.numberKeyboard.delegate = self;
}
self.inputTextField.inputView = self.numberKeyboard.view;
}
self.inputTextField.text = self.valueLabel.text;
self.inputTextField.placeholder = self.titleLabel.text;
self.valueLabel.hidden = YES;
self.inputTextField.hidden = NO;

[self removeConstraints:self.xValueLabelConstraints];
[self addConstraints:self.xTextFieldConstraints];
} else {
[self.inputTextField resignFirstResponder];
self.inputTextField.hidden = YES;
self.valueLabel.hidden = NO;
[self removeConstraints:self.xTextFieldConstraints];
[self addConstraints:self.xValueLabelConstraints];
}
}

- (void)updateConstraints
{
[super updateConstraints];

if (self.editing) {
[self removeConstraints:self.xValueLabelConstraints];
[self addConstraints:self.xTextFieldConstraints];
} else {
[self removeConstraints:self.xTextFieldConstraints];
[self addConstraints:self.xValueLabelConstraints];
}
}

In Swift, programmatically creating UIView and adding controls to it and using auto layout, causes the controls to appear on the view's parent

You haven't got a series of constraints top to bottom, so auto layout can't determine the content size of your object. You have tried to set this via the initrinsicContentSize but you shouldn't need to do this.

You also need to set a horizontal hugging priority for your label to let auto layout know that you want the text field to expand:

I removed your override of intrinsicContentSize and changed your constraints to:

  • Constrain the bottom of the label to the top of the line
  • Constrain the bottom of the line to the bottom of the superview
  • Constrain the baseline of the label to the baseline of the text field
  • Remove the constraint between the top of the text field and the superview
  • Set the horizontal hugging priority of the label.

 func setupConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false

label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.topAnchor.constraint(equalTo: topAnchor)
label.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -8).isActive = true

edit.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true

line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}

I think it is pretty close to what you are after.

How to setup constraints with SnapKit for UITextField and UILabel?

You need

label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentCompressionResistancePriority(.required, for: .horizontal)

Also you can completely remove these 2 lines as by default textfield's ContentHuggingPriority && ContentCompressionResistancePriority is lower than the default for label , plus the textfield has no intrinsic size

How can I arrange two UILabels so that one autosizes and the other takes up the remaining space?

I think this is easier than you might expect...

Constrain "LeftLabel" leading, constrain "RightLabel" trailing, constrain Horizontal Spacing between the two to >= 8, and then give "LeftLabel" a Content Compression Resistance Priority of Low (250).

That tells auto-layout to expand each label based on its text content, but if the distance between them ends up being less-than 8, let "RightLabel" expand to its text, but expand "LeftLabel" only to the point that it is 8-pts from "RightLabel".

Sample Image

Left Label selected:

Sample Image

Results:

Sample Image

what scenarios of layout design needs Dynamic height label and a text field next to each other?

Even though your question is a little unclear, let's see if this helps explain things...

Suppose we start with 2 labels - LabelA (green) and LabelB (cyan):

Sample Image

LabelB is constrained 20-pts below LabelA... as we add text to LabelA, it will grow in height, "pushing" LabelB down and keeping the 20-pts vertical spacing:

Sample Image

Sample Image

Sample Image

All of that is pretty simple, and it's exactly what we expect to happen.

But, suppose we want LabelB to start at 100-pts from the top of the view (the safe-area), and only move down if LabelA gets tall enough?

If we add a Top constraint to LabelB, that will stretch LabelA:

Sample Image

Or, it will compress LabelA:

Sample Image

In both cases because LabelB Top cannot be both 100-pts from the view Top AND 20-pts from LabelA Bottom at the same time.

So, let's change LabelB Top to at least 20-pts from LabelA bottom, by setting it to >=, and, we'll change the LabelB Top constraint to a priority of less-than-required. In this example, we'll use Default High (750).

What we've done is told auto-layout to break the 100-pt Top constraint if needed. So, if LabelA is short, LabelB can be at 100-pts, but if LabelA gets tall, keep LabelB 20-pts below LabelA and break the 100-pt Top constraint:

Sample Image

Sample Image

Sample Image

Sample Image

So, it's not so much the order in which the constraints are evaluated, as it is the logical ability for auto-layout to satisfy all the constraints.


Edit

To try and explain the specific example from Apple...

Let's start with a minimum set of constraints, where the fonts are:

Label - 30pt
Field - 14pt

Sample Image

The Name Label is 40-pts from the Top, and the Name Text Field is constrained to the FirstBaseline of the label.

Now let's change the fonts to this:

Label - 20pt
Field - 30pt

so the Field is taller than the Label, here's what we get:

Sample Image

The Top of the label is still 40-pts from the Top, but the field has been moved up (to maintain the baseline alignment) and the Top of the field is now only (about) 25-pts from the Top.

The goal is to keep the Top of tallest element at 40-pts. With only these two vertical constraints, as we can see, we failed.

So, let's reset the fonts to:

Label - 30pt
Field - 14pt

and add the additional constraints with specified Priorities:

Sample Image

The positioning is identical to the first example, but... if we again change the fonts to:

Label - 20pt
Field - 30pt

this is the result:

Sample Image

The Top of the field is at 40-pts, and the Top of the label has moved down (to maintain the baseline alignment).

And... we've accomplished our goal!


Edit 2

To try to clarify with "plain language"...

The Baseline constraint will control the relative vertical alignment of the two elements. As we change the font size of one, it will make that element "taller" (or "shorter") and the other element will move vertically to keep the baselines aligned.

We've added a required constraint of top >= 40 to each element. That tells auto-layout "don't let either element be closer than 40-pts from the top."

We've added a constraint of top = 40 to each element, with Priority: 249. That tells auto-layout "try to put the top of each element at 40-pts... if you can't (because the baseline alignment is pulling it down), then go ahead and break that top constraint."



Related Topics



Leave a reply



Submit