Dynamic UILabel size iOS 7 issue - ios

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?

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;

Why visual constraints not supporting UITableViewAutomaticDimension?

When i tried with constraints via storyboard its working perfectly .
this is my view controller.
When i was tried with visual constraints i am getting screen like this .
this is my visual constraints :
NSDictionary *viewsdictionary=#{#"txtview":cell.txtview,
#"lbldate":cell.lbldate};
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[txtview]-|" options:0 metrics:nil views:viewsdictionary]];
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[lbldate]-|" options:0 metrics:nil views:viewsdictionary]];
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[txtview]-10-[lbldate(200#1000)]-|" options:0 metrics:nil views:viewsdictionary]];
if([from isEqualToString:_tophonenumber])
{
cell.txtview.layer.borderWidth=2.0;
cell.txtview.layer.borderColor=[UIColor yellowColor].CGColor;
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[lbldate]-10-[txtview]-|" options:0 metrics:nil views:viewsdictionary]];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
self.tableview.estimatedRowHeight = 160;
self.tableview.rowHeight = UITableViewAutomaticDimension;
}
for some reason i am changing the txtview and label but thats also not changing what i did wrong ?
Help me to over come this problem thanks :)
In your IB constraints, I assume that your date has a fixed width, while your text box has a variable width and is just asked to stick to the date leading edge, with the date only adhering from trailing edge to superview trailing? In your programmatic constraints, you do not give that date the fixed width unless a string if statement is satisfied, right? So otherwise, the date has not got the constraints it needs.
Also though, do you call [cell layoutIfNeeded] after updating the constraints? They might need that as well, if you update the constraints on cellForRowAtIndexPath. Theres also a final thing you might need to do with the UITableView so that the table is updated :
[self.tableView beginUpdates];
[self.tableView endUpdates];
If you call this after updating cell constraints, it ensures that the tableview cells are all re-aligned correctly (particular if the height has changed). But I wouldnt call that after each cell has changed - do it once when the tableview has finished updating.

Superview not increasing in height based on the subviews constraint

I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.

AutoLayout two labels within a TableViewCell?

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.

UIScrollView doesn't work with Autolayout (iOS 6)

I made a few UIScrollView's in different views, they all worked without Autolayout.
I turned Autolayout on, because it was better for my app.
But since then, there's a big problem with my UIScrollView's:
No one is scrolling, they don't work.
Here's my code for a UIScrollView:
.m:
-(viewDidLoad) {
scrollerHome.contentSize = CGSizeMake(320, 1000);
scrollerHome.scrollEnabled = YES;
[self.view addSubview:scrollerHome];
scrollerHome.showsHorizontalScrollIndicator = false;
scrollerHome.showsVerticalScrollIndicator = false;
[super viewDidLoad];
}
.h:
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollerHome;
}
Do I have to add some code because I turned on Autolayout?
You should call [super viewDidLoad] before doing anything !
In autolayout, you do not set the contentSize manually. Autolayout works slightly differently with scrollviews, whereby the contentSize of the scroll view is dictated by the constraints of the scrollview's subviews.
If you're trying to force the contentSize to some large size (for example, you're implementing some infinite scroller), you can just add a subview of the appropriate size, e.g.:
UIView *containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:containerView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView);
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(1000)]|" options:0 metrics:nil views:views]];
But if you were trying to set the contentSize in anticipation of adding subviews, you generally don't have to do anything, such as the above snippet. Just add your subviews, provide their constraints, and autolayout will adjust the scroll view's contentSize automatically.
As mentioned above, with autolayout, you can just add the subviews to your scrollview (with their constraints), and the contentSize will be calculated automatically for you.
There is a trick here, though. You sometimes you want to size a subview based upon the dimensions of the screen. But the usual technique of using the | symbols won't work. For example, for an imageview1 inside a scrollview, the usual #"H:|[imageview1]|" won't set the imageview1 to be the width of the screen, but rather it will define the scroll view's contentSize to match the width of imageview1, but it says nothing about what the width of that image view should be!
So, it's useful to capture a reference to the scroll view's superview. That way, you can use something like #"H:|[imageview1(==superview)]|", which not only says "make the scroll view's contentSize equal to the width of imageview1", but also "define the width of imageview1 to be equal to the width of the scroll view's superview."
Thus, for example, to add three images in a paging scroll view, you might do something like:
UIImageView *imageview1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"_DSC0004.jpg"]];
imageview1.contentMode = UIViewContentModeScaleAspectFit;
imageview1.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:imageview1];
UIImageView *imageview2 = ... // configured similar to imageview1
UIImageView *imageview3 = ... // configured similar to imageview1
UIView *superview = self.scrollView.superview;
NSDictionary *views = NSDictionaryOfVariableBindings(imageview1, imageview2, imageview3, superview);
// not only define the image view's relation with their immediate scroll view,
// but also explicitly set the size in relation to the superview, too!
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageview1(==superview)][imageview2(==superview)][imageview3(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview1(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview2(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview3(==superview)]|" options:0 metrics:nil views:views]];
self.scrollView.pagingEnabled = YES;
From the Apple iOS 6.0 release notes:
"In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view."
You can find the full notes here and find the answer to your question in the section that I quoted from. They give code examples on how to use UIScrollView in a mixed Auto Layout environment.

Resources