AutoLayout two labels within a TableViewCell? - ios

I'm fairly new to iOS programming and probably don't understand the view hierarchy as well as I should and thus am failing to successfully get two labels within a custom table cell class I have created to autoresize properly. Namely the "translatesAutoresizingMaskIntoConstraints" property has me a little confused.
I am not using storyboards for this part of the code: I have a TableViewController where I create my own tableView in viewDidLoad. In cellForRowAtIndexPath I init my own TableViewCell implementation.
The problem I'm having is that when I set "setTranslatesAutoresizingMaskIntoConstraints" to NO for the table view and the UILabels I create then add my constraints, I get the following error:
"Terminating app due to uncaught exception `'NSInternalInconsistencyException',` reason: 'Auto Layout still required after executing `-layoutSubviews`. UITableView's implementation of `-layoutSubviews` needs to call super.'"
If I comment out the setTranslatesAutoresizingMaskIntoConstraints lines, my app runs however I get the following warning about the constraints:
"Unable to simultaneously satisfy constraints. 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)"
Essentially what I want to do is to enter code here have the two labels flush against each other and for them to resize based on orientation/device (I will be setting a background colour on them so want them to look 'continuous')
Can anyone help me out and explain what I am missing? Thanks in advance.
My code for adding the labels is:
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 30.0f)];
self.nameLabel.textColor = [UIColor redColor];
self.nameLabel.font = [UIFont fontWithName:#"Helvetica Neue" size:12.0f];
self.nameLabel.backgroundColor = [UIColor brownColor];
[self.nameLabel setText:#"Test"];
// [self.nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.nameLabel];
...
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];

I'm fairly new to ios programming and probably don't understand the
view hierarchy as well as I should and thus am failing to successfully
get two labels within a custom table cell class I have created to
autoresize properly. Namely the
"setTranslatesAutoresizingMaskIntoConstraints" property has me a
little confused.
Well, translatesAutoresizingMaskIntoConstraints is a property that is created by Apple to make the transition from Autoresizing (Spring and Struts) to Autolayout easier. Say, you had some AutoresizingMasks for your view and you just switched Autolayout ON without setting any constraints. Then your existing AutoresizingMasks will get converted into constraints which will hold the view in place. So, by default translatesAutoresizingMaskIntoConstraints property is set to be YES. However, when you start adding constraints, in 90% cases they will conflict with the constraints that got created by converting your AutoresizingMasks. So, it is better to turn it off by setting view.translatesAutoresizingMaskIntoConstraints = NO
In your code, the following might have been creating the problems:
The setting of Frame
You should not set frames to objects which you will be adding constraints to. It's a paradigm shift. When you think in Autolayout way, frames are but effects of setting right constraints who combinedly determine the frame of the view in question.
So, please remove the frame setting.
self.nameLabel = [[UILabel alloc] init]; will suffice.
Setting proper constraints
Your Code:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
NameLabel
Now, the above constraints tells us the nameLabel should be horizontally (as you have not mentioned H: or V:) spaced "standard" distance (20px) from container, adjacent to the summaryLabel.
But what about its Y position and Width and Height?
So we need more constraints.
summaryLabel
Same is applicable for summaryLabel.
So, lets define them properly:
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameLabel(100)][summaryLabel]-|" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[nameLabel(30)]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[summaryLabel]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints3 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[nameLabel(==summaryLabel)]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];
[self.view addConstraints:constraints3];
Now your views will look fine.
At any point of time, to check if your views are missing any constraints, pause the debugger and type the following in the console
po [[UIWindow keyWindow] _autolayoutTrace]
it will show which of your views are having AMBIGUOUS LAYOUT
Also, remember in Storyboard/IB if the constraints are showing as "orange" colour, you need more constraints to define the objects position. Once you have added all necessary constraints, the constraints colours turn to "blue"

First, are you adding constraints to self.contentView after creation?
Second, maybe your constraint set is insufficient for autolayout and it creates own constraints based on autoresizing mask. Try to add vertical constraints and width constraints for labels.

Related

add visual constraints to UITableViewCell so that the UIView occupies the entire cell

I am adding a UIView to a UITableViewCell using the code below
[cell.contentView addSubview:self.mTravelSearchView];
The travel search view appears in the cell. In the interface builder I have added correct constraints to self.mTravelSearchView so everything within this view renders correctly.
However I believe I need to programmatically add constraints so that the mTravelSearchView occupies the entire cell as at present it doesn't.
I have tried the below code, but the width of the view is wrong still and only uses a portion of the cell. What am I doing wrong?
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[travelSearch]|" options:0 metrics:nil views:#{#"travelSearch" : self.mTravelSearchView}]];
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[travelSearch]|" options:0 metrics:nil views:#{#"travelSearch" : self.mTravelSearchView}]];
I'm not really familiar with programmatically adding the constraints.
Nevermind, found that I needed this line
self.mTravelSearchView.translatesAutoresizingMaskIntoConstraints = NO;

Custom UITableViewCell with Image and Label

I have some TableView using a basic or subtitle UITableViewCell and displaying an image/icon. The separator line is automatically indented and starts where the labels are displayed. So far so good. (see correct behavior)
Now I need a couple more labels and an image. Therefore, I created a custom TableViewCell with 4 labels. Without the image they work as expected. But when I add an image to the existing standard ImageView, the image is displayed, the separator line is indented but the custom labels are not. They overlay the image. (see wrong behavior)
Do I have to create a custom ImageView as well to get it working correctly and how is the separator line indented on that way or is there another possibility / state of the art to do something like that?
1. First off create an outlet of all the four custom elements like label and image view as :- lblOne, lblTwo, lblThree, imageView, respectively.
2. Then go to view Viewdidload and create a dictionary :-
NSDictionary *viewsDictionary = #{#" #"superview":self.view,
#"lblOne":self.lblOne,
#"lblTwo":self.lblTwo,
#"lblThree":self.lblThree,
#"imageView":self.imageView
};
3. Now add the Autolayout constraint programmatically :-
NSArray *lblOutletCenterConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[superView]-(<=1)-[lblOne]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:viewsDictionary];
[self.view addConstraints:lblOutletCenterConstraint];
NSArray *lblOutletCenterConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[superView]-(<=1)-[lblTwo]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:viewsDictionary];
[self.view addConstraints:lblOutletCenterConstraint];
NSArray *captionTopConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[lblOne]-25-[imageView]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:captionTopConstraint];
NSArray *captionTopConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[lblTwo]-25-[lblThree]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:captionTopConstraint];
4. Or you can do that by storyboard also.
I finally ended with the solution that I created an empty Interface Builder document, added a TableViewCell, put the Label and ImageView on it, set the constraints, connected everything to the corresponding *.swift file. Finish. I hoped to reuse the ImageView from the default TableViewCell but obviously that did not work correctly (or I was too stupid).

NSLayoutConstraint programmatically set view frame

I know there is are similar topics, but i cant figure out how to do simple thing, for example, set view frame programmatically with Auto Layout constraints. Its pretty easy to do with Storyboard, but when i try to learn about NSLayourConstraint i realise that i'm not understand topic.
Consider that we have UIView and 3 buttons. First button at top have 3 constraints (leading and trailing, and to top of a view). Other 2 buttons centered horizontally with that button and have equal widths. its pretty easy layout, i upload screenshot:
I have read about visual format language, but what i cant understand is - how to create constraint, for example, that relay to top (or trailing-leading)? Like following:
Task look pretty simple but still, i did not found a way how to do that programmatically with NSLayoutConstraint. Could you please provide a solution? Thanks.
Here's a solution (should go in -viewDidLoad). There are a a couple of things to note:
Firstly, VFL doesn't allow you to create all possible types of constraint. In particular, centering needs to be done with the +constraintWithItem: class method on NSLayoutConstraint.
Secondly, as noted in the comments, you could just use hardcoded left and right pad values in the horizontal VFL string to achieve the centering, but this might cause problems if you need to support different device sizes.
Thirdly, the call to -setTranslatesAutoresizingMaskIntoConstraints: is critical. Programmatic Autolayout will completely fail to work if you forget this. Also you need ensure all views are added to their superviews before setting up constraints, otherwise any constraint string referencing a superview will cause a crash
NSArray *names = #[#"button1",#"button2",#"button3"];
NSMutableDictionary *views = [[NSMutableDictionary alloc]init];
for(NSUInteger i=0;i<3;i++) {
UIButton *b = [[UIButton alloc]init];
NSString *name = names[i];
[b setTitle:name forState:UIControlStateNormal];
views[name] = b;
[b setBackgroundColor:[UIColor redColor]];
[b setTranslatesAutoresizingMaskIntoConstraints:false];
[self.view addSubview:b];
}
//List of values to be used in the constraints
NSDictionary *metrics = #{
#"buttonWidth":#150,
#"bHeight":#50, //button height
#"topPad":#100,
#"vPad":#20 //vertical padding
};
//Horizontal VFL string (repeated for all rows).
for (NSString *buttonName in views.allKeys) {
NSString *horizontalConstraintString = [NSString stringWithFormat:#"|-(>=0)-[%#(buttonWidth)]-(>=0)-|",buttonName];
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:horizontalConstraintString options:0 metrics:metrics views:views];
[self.view addConstraints:horizontalConstraints];
//Can't do centering with VFL - have to use constructor instead. You could also hardcode left and right padding in the VFL string above, but this will make it harder to deal with different screen sizes
NSLayoutConstraint *centerConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:views[buttonName] attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.view addConstraint:centerConstraint];
}
//Vertical VFL (vertical spacing of all buttons)
NSString *verticalConstraintString = #"V:|-topPad-[button1(bHeight)]-vPad-[button2(bHeight)]-vPad-[button3(bHeight)]-(>=0)-|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalConstraintString options:0 metrics:metrics views:views];
[self.view addConstraints:verticalConstraints];
[self.view layoutIfNeeded];

Dynamic UILabel size iOS 7 issue

I'm trying resize a label dynamically according to text height. The height can vary from 0 to many lines in the UILabel. I've come up with a solution for this problem that works fine on iOS 8 but fails on iOS 7.1 which I'm trying to support as well.
Autolayout is not being used in this project and all constraints are done programatically.
The code is as follows:
//TableDelegate.m
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 85.0f;
}
//CustomTableViewCell.m
-(UILabel *)commentTextLabel
{
if(!_commentTextLabel)
{
_commentTextLabel = [UILabel new];
_commentTextLabel.numberOfLines = 0;
_commentTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _commentTextLabel;
}
-(void)setupViews
{
[self.contentView addSubview:self.profilePictureView];
[self.contentView addSubview:self.userName];
[self.contentView addSubview:self.timePublishedLabel];
[self.contentView addSubview:self.commentTextLabel];
[self.contentView addSubview:self.seeMoreButton];
self.backgroundColor = [UIColor salooteInputTextBg];
self.contentView.backgroundColor = [UIColor salooteInputTextBg];
NSDictionary *views = #
{
#"picture" : self.profilePictureView,
#"userName" : self.userName,
#"timePublished" : self.timePublishedLabel,
#"text" : self.commentTextLabel,
#"seeMore" : self.seeMoreButton
};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[picture(38)]-5-[userName]-5-[timePublished]-5-|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[picture]-5-[text]-5-|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[seeMore]-5-|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-5-[userName]-5-[text]-5-[seeMore]-5-|" options:0 metrics:nil views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-5-[picture(38)]" options:0 metrics:nil views:views]];
}
-(void)updateConstraints
{
[super updateConstraints];
}
iOS 8 result (left) iOS 7.1 result (right)
I'm not setting any height constraint in my code for the UILabel but rather trying to let the constraints adjust the vertical height for me. If anyone has some input on how to make this work properly on iOS 7.1 I would really appreciate it.
Moving constraints into setupViews produces this: (iOS 7.1 top iOS 8 bottom)
It seems to me you're not adding vertical constraints to the commentTextLabel? You only have this:
//Comment text
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[picture]-5-[text]-0-|" options:0 metrics:nil views:views]];
Try setting a vertical constraint as well--it's likely that you're getting insufficient constraints errors and iOS 8 is guessing the height better than iOS 7. Also, if you're adding constraints to the views, you shouldn't have to call sizeToFit inside the getter.
Autolayout is not being used in this project and all constraints are done programatically.
You're still using Autolayout even if you're adding the constraints only programatically. :)
In response to edits
Your vertical height constraint is insufficient--you only specified the height of the commentTextLabel but not its y-coordinate. Remember that the main objective in Autolayout is to provide a complete set of constraints such that iOS can compute for a view's x, y, width, and height.
I think your constraints are screwed up overall. :) Try adding these rules to the content view instead (I just used 5 for any padding):
H:|-5-[picture(38)]-5-[username]-5-[timePublished]-5-|
H:[picture]-5-[text]-5-|
H:|-5-[seeMore]-5-|
V:|-5-[username]-5-[text]-5-[seeMore]-5-|
V:|-5-[picture(38)]
Also, add your constraints in setupViews--you should only have to add your constraints once and ONLY modify them in updateConstraints. I think updateConstraints is called every time layoutSubviews is called so your constraints keep getting added every time the cell's layout is refreshed.
In response to edits
Your label's word wrap style must be set, too. From inside the commentTextLabel, add
_commentTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
Always set that in conjunction to numberOfLines = 0 if you want a UILabel with a dynamic height.
You also need to right-align your seeMore label (it occupies the full width of the cell minus the padding) by setting that label's alignment property.
And try providing a bigger faux height for now--perhaps 150 or 200 instead of 85, just so we can see all the elements.
For the timePublished label, I forgot to indicate the following vertical constraint:
V:|-5-[timePublished]
I have found that the only way to support both iOS7 and iOS8 easily, is to do the height calculations for each cell yourself using off screen prototypes. The following is an excellent article on the issues. I could find no way to mix auto layout height calculation from iOS8 with manual height estimates for iOS7 in a single code base.
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
The only issue I had with this method was when I used size classes to change the cell font sizes so I could have larger font on iPad etc... This issue is discussed here:
Offscreen UITableViewCells (for size calculations) not respecting size class?

Can't programmatically set NSConstraints on view

I'm trying to do something very simple:
Creating a view from scratch, adding it to the controller's view, and have it stretch edge to edge to its superview, but I get console warning once the app runs, saying the constraints cannot be simultaneously added to the view.
backView = [UIView new];
backView.backgroundColor = [UIColor redColor];
[[self view] insertSubview:backView aboveSubview:tableview];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];
When you create a UIView in code it translates auto-resizing mask to constraints.
i.e. it takes the frame you gave it when you created it and the auto-resizing mask and converts them into NSLayoutConstraints.
These auto-generated constraints are then conflicting with the constraints that you are adding manually.
If I'm right then you will be able to fix it by adding the line...
backView.translatesAutoresizingMaskIntoConstraints = NO;
right after the first line and it should fix the problem.

Resources