Cannot satisfy constraints - Visual Format Language - ios

I started to use autolayout and constraints programatically, but I cannot understand why I get the following warning
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x7fbbd142ead0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7fbbd162ec70(44)]>",
"<NSLayoutConstraint:0x7fbbd1431740 V:|-(20)-[UILabel:0x7fbbd174e520'Liked'] (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>",
"<NSLayoutConstraint:0x7fbbd1431790 V:[UILabel:0x7fbbd174e520'Liked']-(NSSpace(8))-[UILabel:0x7fbbd174e7e0's']>",
"<NSLayoutConstraint:0x7fbbd14317e0 V:[UILabel:0x7fbbd174e7e0's']-(20)-| (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>"
)
I know that the line of code that is causing the error is:
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
but I cannot understand why the constraint cannot be satisfied. This can be a silly question, but I am new to autolayout and the topic is confusing, because of the incomplete documentation. I tried different variations and solutions but failed to find the correct one. What am I missing? I am also pasting the code of my custom table view cell for more detailed inspection.
#implementation NotificationCell
#synthesize nameLbl,notificationLbl,textLabel,timeLbl,profileImgView,notificationImgView,clockImgView,didSetupConstraints;
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
nameLbl = [[UILabel alloc]init];
[nameLbl setTextColor:[UIColor blueColor]];
nameLbl.translatesAutoresizingMaskIntoConstraints = NO;
[nameLbl setTextAlignment:NSTextAlignmentLeft];
[nameLbl setNumberOfLines:1];
[nameLbl setBackgroundColor:[UIColor redColor]];
[self.contentView addSubview:nameLbl];
notificationLbl = [[UILabel alloc]init];
[notificationLbl setTextColor:[UIColor lightGrayColor]];
notificationLbl.translatesAutoresizingMaskIntoConstraints = NO;
[notificationLbl setTextAlignment:NSTextAlignmentLeft];
[notificationLbl setNumberOfLines:1];
[self.contentView addSubview:notificationLbl];
textLabel = [[UILabel alloc]init];
[textLabel setTextColor:[UIColor blueColor]];
textLabel.translatesAutoresizingMaskIntoConstraints = NO;
[textLabel setTextAlignment:NSTextAlignmentLeft];
[textLabel setNumberOfLines:0];
[textLabel setBackgroundColor:[UIColor grayColor]];
[self.contentView addSubview:textLabel];
timeLbl = [[UILabel alloc]init];
[timeLbl setTextColor:[UIColor grayColor]];
timeLbl.translatesAutoresizingMaskIntoConstraints = NO;
[timeLbl setTextAlignment:NSTextAlignmentLeft];
[timeLbl setNumberOfLines:1];
[self.contentView addSubview:timeLbl];
profileImgView = [[UIImageView alloc]init];
[profileImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[profileImgView setBackgroundColor:[UIColor redColor]];
[profileImgView.layer setCornerRadius:20];
[profileImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:profileImgView];
clockImgView = [[UIImageView alloc]init];
[clockImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[clockImgView setBackgroundColor:[UIColor blueColor]];
[clockImgView.layer setCornerRadius:10];
[clockImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:clockImgView];
notificationImgView = [[UIImageView alloc]init];
[notificationImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
[notificationImgView setBackgroundColor:[UIColor purpleColor]];
[notificationImgView.layer setCornerRadius:10];
[notificationImgView.layer setMasksToBounds:YES];
[self.contentView addSubview:notificationImgView];
/*[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_headerView(==80)]-0-[_tableView]-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:elementsDict]];*/
}
return self;
}
-(void)updateConstraints{
if (self.didSetupConstraints == NO) {
NSDictionary *views = NSDictionaryOfVariableBindings(nameLbl,notificationLbl,notificationImgView,textLabel,timeLbl,profileImgView,clockImgView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[profileImgView(40)]-10-[textLabel]-[clockImgView(20)]-[timeLbl(20)]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[profileImgView]-10-[nameLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[profileImgView]-10-[notificationImgView(20)]-[notificationLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[notificationImgView(20)]-[textLabel]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[profileImgView(40)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[clockImgView(20)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[timeLbl]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
[self.contentView addConstraints:constraints];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.textLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.textLabel.frame);
}

A couple of thoughts:
Make sure you are neither explicitly setting rowHeight property on the table view, nor implementing tableView:heightForRowAtIndexPath: in your UITableViewDelegate. Either one of those will establish conflicting constraints with a cell that performs constraint-based height calculations.
While I haven't found it necessary, "Luke said" (in WWDC 2014 video What's New in Table and Collection Views) one should set estimatedRowHeight for the table view (e.g. in viewDidLoad) when using constraint generated row heights.
self.tableView.estimatedRowHeight = 44;
I also haven't found it necessary to do so, but in the same WWDC 2014 video, the presenter, Luke, was getting strange constraint errors similar to yours until he added the following line to his code:
self.tableView.rowHeight = UITableViewAutomaticDimension;
He said that this wouldn't be necessary in the next seed, but in his demonstration, it was necessary.
I've noticed that you can still often receive the unsatisfiable constraints error (even if you've done the above and all of your constraints are fine). I have found that if you change one of your cell's vertical constraints to have a priority less than 1000, it seems to resolve this issue (and the cell appears to be formatted properly.
Note, this behavior of automatically calculated row heights differs in iOS 7. But in iOS 8, the above should do it. If you're still having problems, create a test project that manifests the problem and upload it somewhere where we can look at it.

This happens because the height of your cell is 44 points, however you have vertically placed labels, where only the spaces are 20 + std spacing + std spacing + 20 which is definitely more than 44, and there still is the label height, which you have to take into account: V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|. So you should increase the height of your cell.

you need to set self.tableView.rowHeight = UITableViewAutomaticDimension; It will make tableViewCell height dynamically.
you might also need to set height constraints to label greater then equally so that if not content it's height can be 0.

Some suggestions from my experience with dynamic cell heights:
First, do not override the updateConstraints method in the NotificationCell. instead add your constraints in the init method, after you added all the subviews to the contentView.
Then, change your implementation of heightForRow
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {
NotificationCell *cell = [[NotificationCell alloc] init];
// Do all your configuration here
cell.nameLabel.text = [_names objectAtIndex:indexPath.row];
// Add all the rest too ...
CGFloat cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingSizeCompressedSize].height;
// Add a breakpoint here to see if your constraints are right and if the height is calculated correctly
return height + 1;
}
If your labels are not multiline, even without setting the text, the height should be calculated correctly. However, if they can stretch over multiple lines, then you need to set the value for the text in the heightForRow method.
Hope this fixes your problem.

Related

Cant get constraint to pin UIView to bottom of cells contentView

In the cellForRowAtIndexPath: method of a UITableViewController in my application I need to pin a programmatic UIView to the bottom of each cell's respective cell.contentView. Here's my code:
separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320,15)];
separatorLineView.tag = 17;
separatorLineView.backgroundColor = [UIColor colorWithHexString:#"F0F5F7"];
[cell.contentView addSubview:separatorLineView];
[self.separatorLineView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary* views = NSDictionaryOfVariableBindings(separatorLineView);
NSString *format = #"V:[separatorLineView]-|";
positionYConstraint = [NSLayoutConstraint constraintsWithVisualFormat:format
options:0
metrics:nil
views:views];
heightConstraint = [NSLayoutConstraint constraintWithItem:postSeparatorLineView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeHeight
multiplier:0.01
constant:15];
[cell.contentView addConstraints:positionYConstraint];
[cell.contentView addConstraint:heightConstraint];
[cell.contentView layoutSubviews];
When running it with the setTranslatesAuto...:NO line I cannot see the separatorLineView, not even in the view debugger. When I comment that line out, the separatorLineView is set to the top of the cell.contentView.
Essentially all I need the code to do, is pin the separatorLineView to the bottom of each and every cell's contentView - keep in mind that I have dynamic cell heights.
You need constraints on the horizontal, otherwise it won't know its width and x position. Check this:
UIView * separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320,15)];
separatorLineView.tag = 17;
separatorLineView.backgroundColor = [UIColor colorWithHexString:#"F0F5F7"]; [cell.contentView addSubview:separatorLineView];
[separatorLineView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary* views = NSDictionaryOfVariableBindings(separatorLineView);
NSString *formatV = #"V:[separatorLineView(==15)]|"; // changed this line to set 15 as height and to really pin your view (note I remove the "-" because it adds a 8 margin)
NSArray *positionYConstraint = [NSLayoutConstraint constraintsWithVisualFormat:formatV
options:0
metrics:nil
views:views];
NSString *formatH = #"H:|[separatorLineView]|"; //these are the missing constraints
NSArray *positionXConstraint = [NSLayoutConstraint constraintsWithVisualFormat:formatH
options:0
metrics:nil
views:views];
[cell.contentView addConstraints:positionYConstraint];
[cell.contentView addConstraints:positionXConstraint];

AutoLayout programmatically doesn't work

I want to go into the depths of auto layout and so I want to do it programmatically in order to understand it properly.I know how to use it through storyboard pretty well.Now here is what I am doing(in viewDidAppear:)
-(void)scenario2{
PGView *viewA = [[PGView alloc]initWithFrame:CGRectMake(100, 100, 100, 100) andBackgroundColor:[UIColor blackColor]];
[self.view addSubview:viewA];
PGView *viewB = [[PGView alloc]initWithFrame:CGRectMake(200, 300, 100, 100) andBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
//problem statement
constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
[self.view addConstraint:constraint];
NSLog(#"%#",viewA);
NSLog(#"%#",viewB);
}
So here I have initialized two views with different colors.
I know I need to set the property. translatesAutoresizingMaskIntoConstraints to NO before applying any new constraints. So I have done that.
Now I am specifying heights and widths of both the views.So,the x_position and y_position for both the views are 0.I run the code and I get the expected result. Both views have the specified height and width at point (0,0).
Problem
Now when I am writing the statement "problem statement" to adjust the width of viewB according to the width of viewA,its not working.
Also, when I print views viewA and viewB,it prints the frames as (0,0,0,0).
Can anyone explain me this behavior?
Edit:Here are some modifications that I have made.I have checked the ambiguity of both the views using 'hasAmbiguousLayout' and its working fine now.Now what should be the next step if I want to resize viewB's width thrice to that of viewA's width?
-(void)scenario2{
PGView *viewA = [PGView new];
viewA.backgroundColor = [UIColor yellowColor];
[self.view addSubview:viewA];
PGView *viewB = [PGView new];
viewB.backgroundColor = [UIColor blackColor];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
NSLog(#"%d",viewA.hasAmbiguousLayout);
NSLog(#"%d",viewB.hasAmbiguousLayout);
NSLog(#"%#",viewA);
NSLog(#"%#",viewB);
}
The problem is that you've explicitly set width constraints for A and B to be 200 and 100, respectively. So the additional constraint that specifies that one should have a width of three times the other one is unsatisfiable when combined with the other constraints.
Furthermore, the constraints are ambiguous, as you've defined width and height constraints, but haven't defined where the two frames should be. Yes, you've defined the frame for these two views, but that is ignored when you apply the constraints. You probably shouldn't define the frame values at all, as it's misleading and confusing. But you should set, for example, the leading and top constraints, or specify those in the VFL, or add centerX and centerY constraints. You just want some constraints to dictate where the views should be placed and there's lots of ways to specify that.
And the reason that you're not seeing reasonable frame values at the end is that you've defined the constraints, but they haven't been applied yet. You could examine them in viewDidLayoutSubviews, if you wanted.
You might do something like:
- (void)scenario2 {
PGView *viewA = [PGView new];
viewA.backgroundColor = [UIColor yellowColor];
[self.view addSubview:viewA];
PGView *viewB = [PGView new];
viewB.backgroundColor = [UIColor blackColor];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(viewA, viewB);
// note, I added `|-100-` to say it's 100 points from the top (and 100 tall)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[viewB(==100)]" options:0 metrics:nil views:views]];
// likewise, to say 200 points from the left (and 100 wide)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-200-[viewB(==100)]" options:0 metrics:nil views:views]];
// again, 100 from the top (as well as 200 tall)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[viewA(==200)]" options:0 metrics:nil views:views]];
// and 100 from the left
// but I've removed width definition, as we'll use your multiplier constraint below
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-100-[viewA]" options:0 metrics:nil views:views]];
// now that we've removed the width constraint from the above,
// this should now work fine
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
[self.view addConstraint:constraint];
// no point in doing this, because constraints haven't yet been applied
//
// NSLog(#"%#",viewA);
// NSLog(#"%#",viewB);
}
// if you want to see where they are, you could do that here:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
for (UIView *view in self.view.subviews) {
NSLog(#"%#", view);
}
NSLog(#"-----");
}

UITableViewController Table Header Add a UIButton with Autolayout Constraints

I try to add a UIButton to a UITableHeaderFooterView.
It seems that I can only use specific CGRect frame to pin its position and size in code (<-- did a lot of search on StackOverFlow). And the position is very hard to control, always flying around.
UIButton *scanQRCodeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
scanQRCodeButton.frame = CGRectMake(0.0f, 5.0f, 320.0f, 44.0f);
scanQRCodeButton.backgroundColor = [UIColor redColor];
[scanQRCodeButton setTitle:#"Hello" forState:UIControlStateNormal];
[cell addSubview:scanQRCodeButton];
But what I really want is to use "constraints" like centerX, centerY and proportional width to make it adaptive to different screen size. Then the problem comes with the part "constraintWithItems":
Get the headerView from UITableView:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *headerReuseIdentifier = #"TableViewSectionHeaderViewIdentifier";
// Reuse the instance that was created in viewDidLoad, or make a new one if not enough.
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
if (headerView == nil) {
[tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"TableViewSectionHeaderViewIdentifier"];
headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"TableViewSectionHeaderViewIdentifier"];
}
headerView.textLabel.text = #"MISSION";
headerView.contentView.backgroundColor = [UIColor blackColor];
Create the UIButton on top of the headerView in the same block as above:
UIButton *goBack = [[UIButton alloc] init];
[goBack setImage:[UIImage imageNamed:#"Back"] forState:UIControlStateNormal];
[goBack addTarget:self
action:#selector(back:)
forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:goBack];
Then set the constraints and return the headerView:
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:headerView.goBack
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:0.5
constant:0];
[headerView addConstraint:constraint];
return headerView;
Problem: when I say "headerView.goBack" in "constraintWithItem", the compiler warns me that "Property goBack not found on object of type UITableHeaderFooterView".
Update 1
From Natarajan's answer, I try to do this now:
headerView.translatesAutoresizingMaskIntoConstraints = false;
goBack.translatesAutoresizingMaskIntoConstraints = false;
NSDictionary *metrics = #{ #"width" : #(CGRectGetWidth(self.view.frame) / 10) };
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[goBack]|" options:0 metrics:metrics views:#{#"goBack":goBack}]];
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[goBack]|" options:0 metrics:metrics views:#{#"goBack":goBack}]];
Current Problem: The "metrics" part does not work. I want to have proportional width to the headerView or tableView instead of fixed numbers. Should I use "self.view.frame" (also tried self.tableView.frame)?
Update 2
There is also a problem with the headerView now. If I use "headerView.translatesAutoresizingMaskIntoConstraints = false;", I also need to manually set headerView's position. However, with the following code, headerView can still not be set properly. I tried to add it to tableView first and set it the same way as UIButton but it couldn't work the same way.
[headerView addSubview:goBack];
[tableView addSubview:headerView];
headerView.translatesAutoresizingMaskIntoConstraints = false;
goBack.translatesAutoresizingMaskIntoConstraints = false;
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[goBack(40.0)]|" options:0 metrics:nil views:#{#"goBack":goBack}]];
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[goBack(40.0)]|" options:0 metrics:nil views:#{#"goBack":goBack}]];
[tableView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[headerView]|" options:0 metrics:nil views:#{#"headerView":headerView}]];
[tableView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[headerView]|" options:0 metrics:nil views:#{#"headerView":headerView}]];
I think "goBack" can't accessed as headerView property. Try to find it.
for (UIView *view in headerView.contentView.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
//found button
}
}
Complier warning is correct as UITableViewHeaderFooterView doesn't have the property named "goBack" which you have just added as a subview of UITableViewHeaderFooterView. So Just try to pass the "goBack" button in your constraint code as like below.
To set goBack button fully occupied on HeaderView:
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[goBack]|" options:0 metrics:nil views:#{#"goBack":goBack}]];
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[goBack]|" options:0 metrics:nil views:#{#"goBack":goBack}]];
To set goBack button for specific width and height:
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[goBack(40.0)]" options:0 metrics:nil views:#{#"goBack":goBack}]];
[headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[goBack(30.0)]" options:0 metrics:nil views:#{#"goBack":goBack}]];
Note: Before adding constraint don't forgot to set the headerView.translatesAutoresizingMaskIntoConstraints = false; and goBack.translatesAutoresizingMaskIntoConstraints = false;

How to programmatically add a UILabel with variable height and layout ios

I have been searching for the whole day, but could not make it work.
I have a UIView with two labels, on above the other, on the left side, and one button on the right side. I added the constraints to let autolayout resize views accordingly. Everything was working perfectly when I had set one constraint for the height of the UIView (and no one constraint for the height of the two UILabel), but as the content of the lower UILabel will vary, I removed that constraint and set two constraints for the UILabel, one fixed for the upper UILabel and one with relation "greater than or equal" for the lower UILabel, and other one to fix the distance between the lower UILabel and the UIView.
It looks like auto layout is not capable of calculate the intrinsicContentSize of the lower UILabel, because it never increases its height above 10px, despite the content of the lower UILabel.
UIView *miView = [[UIView alloc] init];
UILabel *miLabel = [[UILabel alloc] init];
[miLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:1];
[miDetailsLabel setText:#"Just one line."];
[miView addSubview:miLabel];
UILabel *miDetailsLabel = [[UILabel alloc] init];
[miDetailsLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:0];
[miDetailsLabel setText:#"Enough text to show 3 lines on an IP4, except first of three UILabels on my test code, with no content"];
[miView addSubview:miDetailsLabel];
UIButton *miButton = [[UIButton alloc] init];
[miButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[miView addSubview:miButton];
NSArray *constraint_inner_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[label]-(10)-[button(52)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"button":miButton}];
NSArray *constraint_inner2_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[details]-(10)-[button]-(0)-|" options:0 metrics:nil views:#{#"details":miDetailsLabel, #"button":miButton}];
NSArray *constraint_label_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[label(18)]-(2)-[details(>=10)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"details":miDetailsLabel}];
NSArray *constraint_button_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(22)]" options:0 metrics:nil views:#{#"button":miButton}];
[miView addConstraint:[NSLayoutConstraint constraintWithItem:miButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[miView addConstraints:constraint_inner_h];
[miView addConstraints:constraint_inner2_h];
[miView addConstraints:constraint_label_v];
[miView addConstraints:constraint_button_v];
I ve put a reduced version of the code.
any ideas of what I am missing?
Thanks
UPDATE : Thanks to Matt advice, I've tried this solution to set the proper value to preferredMawLayoutWidth:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSArray *allKeys = [dictOpcionesTickets allKeys];
for (NSString *key in allKeys) {
NSArray *tmpArray = [dictOpcionesTickets objectForKey:key];
if (![[tmpArray objectAtIndex:0] isEqual:#""]) {
UILabel *tmpLabel = [tmpArray objectAtIndex:3];
tmpLabel.preferredMaxLayoutWidth = tmpLabel.frame.size.width;
NSLog(#"width: %f",tmpLabel.frame.size.width);
}
}
[self.view layoutIfNeeded];
}
To explain the code, as I said, I'm doing it dynamically, so I've created a dictionary with an array with the references to my UILabel (among other interesting information). When I run the code, I get the next log (with the code parsing 3 UILabel, first label with no content):
2014-12-28 20:40:42.898 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:42.912 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:43.078 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.080 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.326 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.327 myapp[5419:60b] width: 229.000000
But I'm still getting the same result...the UIView is still showing a height equals to the minimum height set by the constraints, not showing the content of the UILabel.
UPDATE 2 Still fooling around.
I've tested my initial code but simplified on a fresh project in xcode 6, running on an actual iPhone 4 with iOS7, and it worked perfectly without setting preferredMaxLayoutWidth or subclassing UILabel, and even without calling layoutIfNeeded on parent view. But when it comes to the real project (I think it was originally builded in xcode 4 or 5) it does not work:
- (void)addLabelsDinamically {
self.labelFixed.text = #"Below this IB label goes others added dinamically...";
UIButton *miBoton = [[UIButton alloc] init];
miBoton.translatesAutoresizingMaskIntoConstraints = NO;
[miBoton setTitle:#"Hola" forState:UIControlStateNormal];
[self.viewLabels addSubview:miBoton];
[self.viewLabels addConstraint:[NSLayoutConstraint constraintWithItem:miBoton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.viewLabels attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[boton(22)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[boton(40)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
UILabel *miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":self.labelFixed}]];
UIView *pre = miLabel;
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
}
Ah, on the same screen I have one UILabel added on IB, with numberOfLines set to '0', vertical constraint "greater or equal than" and horizontal constraint set in a similar way (but both constraints set on IB)...and it works perfectly...this is driving me nuts!! any help???
Well, finally I've found the error, as I stated in the "UPDATE 2", it was very strange that it was working on an UILabel added via IB, but not with those added dynamically.
So, I looked again to my screen on IB, and found that I have an height constraint with "equal" relation, when I changed it to "greater or equal than" it all started to work!!!! Why I did not notice that the constraint was not "greater or equal than"? Because the view with the constraint was getting larger and larger as I was adding views with labels inside. Is that a bug?
Well, to summarize if someone find this useful. "How to add UILabels multi-line which adapts to its content?"
In iOS 6 versions it was a bug with "preferredMaxLayoutWidth", not getting the width once it was "layouted", and as a result of that programmers must set it via code (the best solution is doing it dynamically, an example of that I put it on my above question).
Fortunately on iOS7 and later, this has been solved, and you can rely only with constraints.
Adding via IB
Add the UILabel, and set "number of lines" to '0' (this will make the UILabel use as many lines as it needs).
Add the constraints, you need at least 3 constraints (pin to top or bottom, and both sides), the fourth will be height with a relation "greater or equal than".
Be sure than the view container has enough room, or also has a height constraint with a relation "greater or equal than".
Adding via code (just paste the code posted above):
UILabel *miLabel = [[UILabel alloc] init]; // Initialitze the UILabel
miLabel.translatesAutoresizingMaskIntoConstraints = NO; // Mandatory to disable old way of positioning
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0; // Mandatory to allow multiline behaviour
miLabel.text = #"ñkhnaergñsdbmnñgwn"; // big test to test
[self.viewLabels addSubview:miLabel]; // Add subview to the parent view
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel}]]; // horizontal constraint
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel}]];
UIView *pre = miLabel;
// Adding a second label to test the separation
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbnoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
Thanks to those who has tried to help me!! This site is amazing :)
This answer will work in a programmatic situation when you are added the text information dynamically and if this above is on a UICollectionViewCell or UITableViewCell.
In these situations you will need to update the layout and constraints on the contentView, then set the preferedWidth:
-(void) layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
//locationName.preferredMaxLayoutWidth = locationName.frame.size.width;
//locationDetails.preferredMaxLayoutWidth = locationDetails.frame.size.width;
[super layoutSubviews];
}
This is similar to other answers where u are getting the new frame information and using that for the preferredMaxLayoutWidth then updating the view.
You will need constraints added to those views for the vertical as a start point and the UILabel need their numberOfLines = 0
On second thoughts setting he preferredMaxLayoutWidth in the layoutSubViews is poor solution if your width changes, as well as the height. Best to set this else where like the init for that cell. In some ways it might not be needed if you are constraining the width. This in my situation lead to the preferredMaxLayoutWidth changing each time that cell was set.

How do I make my superview resize to match the size of its subviews with Autolayout?

I've got a UIView ("superview") that has a couple of UILabels as subviews, set up in Interface Builder. I've got Auto-Layout turned on to properly space all the labels in a list, one after another. This works well.
What I'm trying to do now is make it so that my Superview resizes vertically to match the height of all my labels and I can't quite figure out how to do this.
How does this work?
Here's an example using visual layouts... so ignore the translatesAutoresizingMaskIntoConstraints business.
This relies on the fact that the container view does NOT have any height constraints and it seems to rely on the spacing between views. The system will resize the view to match the requirements of the subviews.
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelOne = [[UILabel alloc] init];
_labelOne.text = #"Banana";
_labelOne.backgroundColor = [UIColor blueColor];
[_labelOne setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelTwo = [[UILabel alloc] init];
_labelTwo.text = #"Dinosaur";
_labelTwo.backgroundColor = [UIColor yellowColor];
[_labelTwo setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:_labelOne];
[self addSubview:_labelTwo];
NSDictionary *views = NSDictionaryOfVariableBindings(_labelOne, _labelTwo);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelOne]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelTwo]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[_labelOne(30)]-15-[_labelTwo(30)]-10-|" options:0 metrics:nil views:views]];
}
return self;
}
I know the basic idea is to have the intrinsic content size of your subviews drive the height of superview by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added.
Try use the UIView method sizeToFit?

Resources