I am trying to add a UIlabel to a UIView class.
it should be in the following format -15-Label(stretch to max width)-15.
Top spacing=15 and height fixed to 30.
Two issues with the following code:-
1) Label does not stretch to max width
2) Right side spacing does not show up , if the text it too long.
-(void)awakeFromNib{
[super awakeFromNib];
view1 =[[UILabel alloc] init];
view1.translatesAutoresizingMaskIntoConstraints=NO;
[self addSubview:view1];
view1.text= #"Hello";
NSDictionary *constraintViews=
#{#"view1":view1};
NSDictionary *metrics=#{#"spacing":#(15)};
NSArray *hConstraints=[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-spacing-[view1]-spacing-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:allViews];
NSArray *vConstraints=[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-spacing-[view1(30)]" options:0 metrics:metrics views:constraintViews];
[self addConstraints:hConstraints];
[self addConstraints:vConstraints];
}
1)
Update the horizontal constraints like so:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-spacing-[view1]-spacing#751-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:constraintViews];
Try adding the line below:
[view1 setContentHuggingPriority:UILayoutPriorityHigh forAxis:UILayoutConstraintAxisHorizontal];
2) I always set the numberOfLines property of a label to 0 by default, so that the label will autoresize vertically if the text needs to be shown in two or more lines. That being said, you would need to remove the fixed height constraint and the label will be the size of the it's contents like so:
view1.numberOfLines = 0;
NSArray *vConstraints=[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-spacing-[view1]" options:0 metrics:metrics views:constraintViews];
I hope this helps.
I used this generic method for applying constraints of childView wrt to ParentView.Just pass your views to this method.
+ (void)applyConstraints:(UIView *)pChildView withSuperView:(UIView *)pParentView {
pChildView.translatesAutoresizingMaskIntoConstraints = NO;
// Width.
CGFloat widthValue = pParentView.frame.size.width;
[pParentView addConstraint:[NSLayoutConstraint constraintWithItem:pChildView attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual toItem:pParentView
attribute:NSLayoutAttributeWidth multiplier:1.0 constant:widthValue]];
// Height.
CGFloat heightValue = pParentView.frame.size.height;
[pParentView addConstraint:[NSLayoutConstraint constraintWithItem:pChildView attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual toItem:pParentView
attribute:NSLayoutAttributeHeight multiplier:1.0 constant:heightValue]];
// X margin.
[pParentView addConstraint:[NSLayoutConstraint constraintWithItem:pChildView attribute:NSLayoutAttributeCenterXWithinMargins
relatedBy:NSLayoutRelationEqual toItem:pParentView
attribute:NSLayoutAttributeCenterXWithinMargins multiplier:1.0 constant:0]];
// Y margin.
[pParentView addConstraint:[NSLayoutConstraint constraintWithItem:pChildView attribute:NSLayoutAttributeCenterYWithinMargins
relatedBy:NSLayoutRelationEqual toItem:pParentView
attribute:NSLayoutAttributeCenterYWithinMargins multiplier:1.0 constant:0]];
}
Related
I'm trying to understand how auto layout works programmatically. I know we can assign it in the Interface Builder but I want to learn more about how it works underneath.
If someone could show me the code to make a UIImageView pin it's top, left, right, and bottom alignments (as well as equal width and equal height) that would be great!
yourView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(yourView);
[superview addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[yourView]|"
options:(NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing)
metrics:nil
views:views]];
[superview addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[yourView]|"
options:(NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom)
metrics:nil
views:views]];`
You can apply the constraints using native NSLayoutConstraints as follows:
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:#[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
This would be very complex for the beginners, one easy way to achieve this with few lines of code is to use autolayout wrappers like Masonry.
With Masonry, you can do the same as above :
Heres the same constraints created using MASConstraintMaker
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
Or even shorter
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
In my new app, I am trying to adopt AutoLayout throughout. In one of my table views, I have an image view and a label. If I do this in my controller's viewDidLoad: method
self.tableView.rowHeight = UITableViewAutomaticDimension;
the row is not high enough to accommodate the image. (I have added fixed width and height constraints to the image view).
If I remove the statement, then the height of the cell is the height as set in IB.
Is there a method I have to implement in my UITableCell class to tell the AutoLayout system my minimum height requirement? Or did I do something wrong?
Well, I could never figure this out using IB. However, I did get success adding the constraints manually, so I thought it worth sharing my complete solution. (Remember: an image view, a label, dynamic text). I want my image to always be 80 by 80, no matter how big the content gets.
Enjoy.
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder:aDecoder]) ) return nil;
// TODO: Add an imageview and populate it
_titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,300,20)];
_titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
_titleLabel.numberOfLines = 0;
[self.contentView addSubview:_titleLabel];
_albumArtImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
[_albumArtImageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:_albumArtImageView];
NSMutableArray* constraints = [NSMutableArray new];
UIView* contentView = self.contentView;
[_titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
// _titleLabel to contentView
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_titleLabel
attribute:NSLayoutAttributeFirstBaseline
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTop
// as font grow, space from top grows
multiplier:1.8
constant:30]];
// similarly for bottom
[constraints addObject:[NSLayoutConstraint
constraintWithItem:contentView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:_titleLabel
attribute:NSLayoutAttributeLastBaseline
multiplier:1.3
constant:8]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:contentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
// Now the imageView
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_albumArtImageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_albumArtImageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:0
multiplier:1.0
constant:80]];
// for horizontal
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-2-[_albumArtImageView]-5-[_titleLabel]-15-|"
options:0 metrics:nil views:NSDictionaryOfVariableBindings(_titleLabel,_albumArtImageView) ]];
[self.contentView addConstraints:constraints];
return self;
}
I have two UILabels inside of a XIB, and I want to position one label underneath of another label. That said, the top label's height (descriptionLabel) varies. Does anyone know how I can go about doing this? I feel like I've tried everything.
Here is the code for my Labels so far; I want to position my second label (bodyLabel) about 25 pixels below descriptionLabel (regardless of how long descriptionLabel is):
CGRect frame = descriptionLabel.frame;
frame.origin.y=400;//pass the cordinate which you want
frame.origin.x= 12;//pass the cordinate which you want
descriptionLabel.frame= frame;
CGRect frame2 = bodyLabel.frame;
bodyLabel.frame= frame;
do this in viewDidLayoutSubviews;
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* set label1's frame first */
CGRect newFrame = _label2.frame;
newFrame.origin.y = CGRectGetMaxY(_label1.frame)+25;
_label2.frame = newFrame;
}
CGRectGetMaxY takes the frame's origin into account when returning a value. keep in mind that frames are not yet set for views if you're doing things in loadView or viewDidLoad, this could be why things keep ending up with a 0 origin - they are still 0 at that time.
Suppose you have two UILabels. Say, firstLabel and secondLabel. Suppose you have set the first frame like so:
firstLabel.frame = CGRectMake(0,0,50,50);
If your first frame dynamically changes its height, and if want your secondLabel to be always under the first, you can set the y coordinate of the secondLabel in such a way it is always under it. The code for it can be something like:
secondLabel.frame = CGRectMake(0,firstLabel.frame.size.height,50,50);
Using this, the y position of the secondLabel is dynamic and is dependent on the firstLabel's height.
In your case, the position of the bodyLabel can be :
CGRect bodyLabelFrame = CGRectMake(0,descriptionLabel.frame.size.height,50,50);
bodyLabel.frame = bodyLabelFrame;
Have you tried using autolayout? Using autolayout, this is how I might do this if the superview for the labels was superView:
// This is necessary to use autolayout
descriptionLabel.translatesAutoResizingMasksIntoConstraints = NO;
bodyLabel.translatesAutoResizingMasksIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(descriptionLabel, bodyLabel);
// this will pin the top of bodyLabel to the bottom of the descriptionLabel with a gap of 25px
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[descriptionLabel]-25-[bodyLabel]" options:nil metrics:nil views:views]];
You'll need to do it in code. I use auto-layout.
First, create two private NSLayoutContstraint variables for your two labels — you'll use these to adjust your label height when you set the text.
#interface CustomView ()
#property (strong, nonatomic) NSLayoutConstraint *firstLabelHeightCn;
#property (strong, nonatomic) NSLayoutConstraint *secondLabelHeightCn;
#end
Second, define the first labels X, Y, and width — the height will be set depending on the text you set in it.
NSLayoutConstraint *cnX;
NSLayoutConstraint *cnY;
NSLayoutConstraint *cnWidth;
// first label
_firstLabel = [[UILabel alloc] init];
_firstLabel.lineBreakMode = NSLineBreakByWordWrapping;
_firstLabel.numberOfLines = 0;
_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_firstLabel];
cnX = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:H_MARGIN];
cnY = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:V_MARGIN];
cnWidth = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:-H_MARGIN];
_firstLabelHeightCn = [NSLayoutConstraint constraintWithItem:_firstLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0 constant:0];
[self addConstraints:#[ cnX, cnY, cnWidth, _firstLabelHeightCn ]];
Third, define the second labels X, Y, and width off the first labels properties. For the Y position you'll want to set the second labels TOP to the first labels BOTTOM (+ any margin).
// second label
_secondLabel = [[UILabel alloc] init];
_secondLabel.lineBreakMode = NSLineBreakByWordWrapping;
_secondLabel.numberOfLines = 0;
_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_secondLabel];
cnX = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
cnY = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeBottom multiplier:1.0 constant:V_MARGIN];
cnWidth = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:_firstLabel attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
_secondLabelHeightCn = [NSLayoutConstraint constraintWithItem:_secondLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0 constant:0];
[self addConstraints:#[ cnX, cnY, cnWidth, _secondLabelHeightCn ]];
Finally, create two methods to set the text for your two labels. These methods will take the incoming text, calculate the height, adjust your layout constraint constants, and then set the actual text in the label. Since you're using auto-layout once you change the text/height of the first label, the second label will automatically adjust.
- (void)setFirstText:(NSString *)firstText
{
_firstText = firstText;
if (_firstText.length) {
_firstLabelHeightCn.constant = [CustomView textHeight:_firstText width:self.bounds.size.width font:_firstLabel.font];
_firstLabel.text = _firstText;
} else {
_firstLabelHeightCn.constant = 0;
_firstLabel.text = nil;
}
}
- (void)setSecondText:(NSString *)secondText
{
_secondText = secondText;
if (_secondText.length) {
_secondLabelHeightCn.constant = [CustomView textHeight:_secondText width:self.bounds.size.width font:_secondLabel.font];
_secondLabel.text = _secondText;
} else {
_secondLabelHeightCn.constant = 0;
_secondLabel.text = nil;
}
}
Here is a real-life example:
ContextView.h
https://gist.githubusercontent.com/rosem/4fc7f9ed80c114ba45a0/raw/05f46c0340e1682823d6bbeb95f8b084ba4449d5/gistfile1.mm
ContextView.m
https://gist.githubusercontent.com/rosem/6d768776991569496ab6/raw/76ce4f47b3f86555ee4755e7d52d12511adcec27/gistfile1.m
I am trying to programatically setup some constraints. I have one container view UIView which holds three subviews.
UIView - circleView
UILabel - label1
UILabel - label2
The circleview is shown at the top of the container at (0,0,width,80). The label1 is shown underneath the circleview with 5.0 padding.
I am now trying to add the label2 to be in the center of the circleView. How do I do this with AutoLayout programatically.
This is what I currently do.
NSDictionary *views = NSDictionaryOfVariableBindings(circleView,labelView, iconLbl);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[circleView(circleSize)]|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[labelView]|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[circleView(circleSize)]-(padding)-[labelView]-|" options:0 metrics:metrics views:views]];
The label2 is the iconLbl view in the dictionary.
This should be relatively straightforward - it helps to use xib to see how many constraints you actually need to get the effect you want. Constraining a label to be in the center of another view, both of which are in a parentView, only requires 2 constraints to be fully constrained. If this were a regular UIView, you'd need 4 constraints (x,y,width,height), but the label will automatically determine it's width and height from it's content, so it's not ambiguous. This is of course if you other views are all properly constrained, but you only asked about the label2 in the circle view.
I prefer to use the non-visual form for defining constraints because they read like mathematical equations. What you want is:
label2.centerX = circleView.centerX*1 + 0;
label2.centerY = circleView.centerY*1 + 0;
Since these are siblings with a common parent, the constraints are added to the parentView. So you get the following two constraints.
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:circleView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:circleView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
This is sufficient for getting label2 centered in the parentView. Any issues you get will likely be due to other constraints between your views not being properly specified.
Can you try this?
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[circleView(circleSize)]" options:0 metrics:metrics views:views]]; //Dont link to both the sides. Dock to the left edge
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self. labelView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0 ]]; //Specify the X
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self. labelView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0 ]]; //Specify Y
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[circleView(circleSize)]" options:0 metrics:metrics views:views]]; //Dock the circle to the top
With Masonry library
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(view);
}];
What I want is to add an image as a subview, then align it centered along the X axis and 10 points from the bottom of the superview. I need to use Auto Layout only, and preferably visual formatting language.
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
[self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.imageView setImage:[UIImage imageNamed:#"06-arrow-south"]];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:self.imageView];
[self addConstraints];
self.imageView.layer.borderColor = [[UIColor redColor] CGColor];
self.imageView.layer.borderWidth = 1.0;
}
- (void)addConstraints {
NSDictionary *viewsDictionary = #{#"arrowImage":self.imageView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[arrowImage(==40)]-|"
options:0
metrics:nil
views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[arrowImage(==40)]-10-|"
options:0
metrics:nil
views:viewsDictionary]];
}
Here's what I'm getting:
V:|-[arrowImage]-10-|
This aligns the image view so that it is the standard length (20pt) from the top of its superview, and 10 from the bottom. What you want is to PIN it to the bottom only:
V:[arrowImage]-10-|
I'm not sure that centering in the superview can be done with visual format, but you can create a single constraint to center it:
[self.view addConstraint:
[NSLayoutConstraint
constraintWithItem:self.imageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
There's no need to set the height or width of the image view; its size will be determined from its content.
So, here's the full code for your addConstraints method:
- (void)addConstraints {
[self.view addConstraint:
[NSLayoutConstraint
constraintWithItem:self.imageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
NSDictionary *viewsDictionary = #{#"arrowImage":self.imageView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[arrowImage]-10-|"
options:0
metrics:nil
views:viewsDictionary]];
}
What you currently doing is saying arrowImage should be the full size of the view minus 20px on left, right and top but be minus 10px from bottom.
The to center on x do the following.
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:arrowImage attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
Then as #Austin points out remove the need to be minus 8 from top and be minus 10 from the bottom:
V:[arrowImage]-10-|
Btw its minus 20 as default when you connect a sibling view to a parent: (see comment below )
|-[