I have a table view cell subclass that has three UILabels stacked on top of each other - the top label has numberOfLines = 0, the middle one has 3, the bottom has 1. In 99% of the cases iOS 8 works perfectly fine; however, there are some scenarios where, if in the top label I set some text that is just on the point where the label wants to wrap, weird things begin to happen.
Essentially, the middle label will disappear, or show maybe 0.33pts of its height. Autolayout trace shows there's some ambiguous constraints. Obviously I want to show all the labels and for autolayout to expand the cell based on the content.
My vertical hugging/compression priority on all three labels is 1000/750.
As I said, I can't tell if it's my constraints being wrong, iOS 8 being different to 7 (but intended), or iOS 8 being buggy.
I am setting preferredMaxLayoutWidth on all labels in the cell's layoutSubViews method to their respective frame widths. I can "fix" this issue by reducing preferredMaxLayoutWidth by 20pts or so - this then causes the "wrap" on the label to alter. It's not really a fix because I can take a character or two out of the text and it'll bug out again because the wrap has altered.
In my heightForRowAtIndexPath I have an off-screen cell and do:
prototypeCell.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(prototypeCell.bounds));
[prototypeCell layoutIfNeeded];
One more thing: Using the resizable iPhone I can set the screen width to 320pts and things break, but when I slowly increase the width of the screen, things work again. This is what leads me to believe it's the wrapping on the UILabel.
I have created a Github project, showing what's going on with the cells/labels:
https://github.com/gbrhaz/BrokenTableViewCells
Related
In my UITableViews I have a mixture of built-in cells and custom cells that mimic the look of built-in cells. For example, I use Left Detail Cell UITableViewCells as well as custom cells that also LOOK like a Left Detail Cell but have custom elements (like a left UILabel, but a textfield instead of the right UILabel and maybe other elements like a checkmark image to let the user know when they have entered valid info).
I originally set up the constraints on my custom cells so that blue left-side label would have a width constraint = 91 because that is what the built-in cell's label's width was.
However, for the last few versions of Xcode, it has been complaining at me about "Fixed width constraints may cause clipping."
I've seen all of the myriad Stack Overflow posts telling me to just make my constraint <= or >=, but that does NOT do what I want. I don't want those labels to get bigger, because then the various rows will not have a consistent width on those left detail labels. The screenshot below wouldn't have a consistent layout from row to row.
So, how can I set my constraints on that left detail UILabel so that it will match with the built-in Left Detail Cell's sizing behaviors without getting that stupid warning clogging up my build results?
I tried setting a proportional width on the label, but when I rotate the device my label gets much wider while the built-in Left Detail cell's detail label stays the same width and my UI looks janky because they aren't lined up anymore.
I tried making my fixed width constraint a lower priority (750) and adding a second <= constraint at priority 1000, but then it just complained about both constraints.
Example below:
I'm wrestling trying to get some multiline labels behaving inside tableview cells. I've already moved away from stacks that didn't work reliably at all as described in this question
UILabel inside nested UIStackViews inside UITableViewCell sometimes truncating
I fixed one of my views with the help of the comments on that question but another view just wouldn't work for me, even after moving to an autolayout non-stackview setup. I then ended up moving the cell that i couldn't get to work into the view which was working with similar layout and got to a point where i have two cells in the same view, one that worked and one that didn't. I've exported this into a new test app which I've uploaded here
In this app there is a simple tableview with 2 cells within it. One cell displays the large multiline text properly and expands the cell as required. The other cell stops short and truncates the multiline label as you can see here
The two cells to me seem pretty much identical in their constraints so I'm very confused why one works and the other doesn't. Here's an overview of their constraints
There are a few constraints not installed as i've been experimenting trying to figure out what's causing one of the cells to not work.
If anyone could explain to me what is causing these two cells to not behave the same, or more importantly why one of the labels doesn't fill its table cell that'd be really appreciated as I've spent hours looking and just can't seem to figure it out.
Cheers!
Edit:
To explain what went wrong in the first place...
The Right-side "Tags Label" in Cell2 has an Explicit Preferred Width = 300. I don't know the internals, so can't say exactly what's happening, but I get the impression Auto-Layout will take that Preferred Width value into consideration when calculating the text bounding-box height, and then continue on with constraints, content, intrinsic size, etc.
Simply un-checking that explicit option will fix the issue.
Original Answer:
I found it easier to start a new Cell from scratch, rather than try to modify the constraints you had set up, so... This will hopefully be reproducible.
Prep: Add new “Cell3” class; make basic edits to code to accommodate Cell3. I also find things easier if I make some variables for label values and set background colors of elements for easy visual inspection and testing.
Step 1: Add a new prototype; purple background; TableViewCell3 class and “Cell3” reuse ID; stretch it vertically to make it tall enough to work with (it won't affect run-time height).
Step 2: Add a UIView for the Left Side labels. Leading = 8 to Superview. Width = 200; Height = 100; Center Vertically. The Height and Width values will be changed later.
Step 3: Add the two Left Side labels - 1 and 2 (Body font) - to the UIView. Constrain 1 Left = 0; Top = 0. Constrain 2 Left = 0; Bottom = 0.
Step 4: Add a vertical spacing constraint from 2 to 1 of 7.5 and change the Height constraint of the UIView to >= 20 (runtime will likely always exceed 20).
Step 5: Change the Width constraint of the UIView to >= 40 (runtime will likely always exceed 40); Add Trailing Space to Container constraints for both Left Side labels, and set them to >= 0.
Step 6: Add Top and Bottom “to Superview” constraints to the UIView of >= 0.
Step 7: Add the Right Side label (Caption 1 font, number of lines 0). Constrain Top >= 0, Right = 0, Bottom >= 0, all to Superview; also Center Vertically.
Step 8: Add a Horizontal spacing constraint from Right Side label to UIView, set to 20. Give Right Side label a Width constraint = 40 (runtime will likely always exceed 40), and set the Priority to 250. This allows the UIView containing the Left Side labels to be in control of the widths.
Step 9: Assign IBOutlets and run the app. Try changing up the text. Make the left side labels shorter or longer... try setting the right side label to only enough text for one line... etc.
At this point, things should look pretty good - until... you put too much text in one of the left side labels, at which point you'll have a very, very narrow, very very tall right side label. So...
Step 10: Add another Width constraint to the UIView and set it to <= 200. Depending on your actual content, you may want to modify that - or perhaps set it to <= to a percent of the width of the cell.
I updated my original GitHub repo so you can check it out. It should have a commit for each "step" listed above, which might make it easier to follow along - https://github.com/DonMag/CellTest2
Results:
I've seen a lot of guides about self-sizing cells in iOS 7 and 8. Unfortunately, all of them shows very simple cases (I mean two UILabel's with same width and which placed like the first under the second and their constraints are very simple too). So I've a situation that seems to be not so much difficult but I can't resolve a problem.
I believe that it is very easy for the most of people and hope that somebody can help. The problem is that there is UITableViewCell with 3 child views inside.
Every view is UILabel. Labels placed in such order: two labels at the top of UITableViewCell with fixed size and one at the bottom with fixed width but with dynamic height. How should I setup my constraints properly to make my UITableViewCell be self-sizing?
The key to self-sizing is a vertical set of constraints that will determine the cell height. This isn't that complex, as you don't have two horizontal labels that vary in height.
Since headerLabel and dateLabel height don't vary, you only need to constrain the dynamicLabel to one of the two top labels.
In this example, we'll arbitrarily pick headerLabel to use in the vertical constraints. The spacing between the labels wasn't specified, so I'll assume it's 10. Adjust as necessary.
For your horizontal constraints, set leading and trailing space to the superview.
Your vertical constraints would simply look like "V:|-10-[headerLabel]-10-[dynamicLabel]-10-|" (and should
dynamicLabel will grow as tall as it needs, provided you set its numberOfLines to 0, and its height will end up determining the height of the cell.
Assuming the constraints are setup properly, and the storyboard cell height exactly matches the vertical height set by the constraints, you shouldn't see any storyboard warnings or errors, and should be good to go.
8.4 does address a number of issues, so you shouldn't require any specific (layoutSubViews/preferredMaxLayoutWidth/reloadData) code to work around earlier 8.x problems.
As an aside, a general tip is to pin your constraints to the superview margin, instead of the superView. This means your leading, trailing, top, and bottom constraints could be 2, instead of 10, since the margin is generally 8. This lets your white space adapt to different devices, which is a really nice touch.
Having a strange issue using Auto Layout with a UILabel where it resizes almost to its content size less one line (driving me nuts). So basically, it seems to get to where it should minus a few pixels causing an ellipses at the end when downsizing to the iPhone 5 or 6. It works fine on the 6+, which is what the XIB is sized for.
Here's what the XIB looks like and what I have the constraints set to. The label is the "Big long label here is the event description".
Based upon this stack post, I thought I was going to figure it out. I tried both methods mentioned in the post. All four of the constraints in the screenshot are set to 1,000. If I do what the post says and add a height constraint with priority of 500, which is less than the vertical hugging and vertical compression resistance priorities, it still doesn't work. I've also just not set the height constraint, kept the preferred width set and had leading and trailing space constraints set, which was mentioned in the comments. The number of lines is set to 0.
To try to give more detail, the UILabel is in a UIView set as contentView. I then add the contentView to a UIScrollView. The UIScrollView is being automatically sized by pinning the contentView at 0 to all four sides. The contentContainer is also constrained to the screen width (not ideal but only way I've been able to get it to work - not sure if it has anything to do with this issue). I'm not getting any constraint issues in the console. translatesAutoresizingMaskIntoContraints is set to NO for the contentView. Here's what the UILabel looks like when ran:
It should be one line longer at the bottom to fit. Someone please help me!
I think I might have solved this, but the solution seems hacky. It seemed to be an issue of setting the preferredMaxLayoutWidth. It worked fine for what I had set for the iPhone 6+, but for some reason almost got there with significantly less width on the iPhone 5/6. Just added this:
self.eventDescriptionLabel.preferredMaxLayoutWidth = self.eventDescriptionLabel.frame.size.width;
And now it works fine.
I'm creating an iOS view that displays various static text elements. The xib looks like this:
It uses four labels for the title, timestamp, body, and footer. Every view is anchored to the sibling view above it vertically and anchored to the left/right of the parent view. All labels have a fixed height except the body which has a >= height and the number of lines set to 0 with "word wrap" as the line wrapping style. The parent view is a UIScrollView.
On the iPhone it looks like fine:
However on the iPad it looks like this:
Huh? Where is all that extra vertical space in the body label coming from? The xib and its view controller are identical between iPhone and iPad (there is no custom iPad code at the moment). I've found that the vertical space is directly related to how many line-wraps the label renders. If no lines wrap, no extra vertical space. If only a few lines wrap, there's a little extra vertical space. If nearly every line wraps, well, that's what it looks like.
First of all any ideas on why UILabel is behaving this way?
Second of all, if I can't make it stop doing this how can I work around it?
I've already tried a few things. If I call [bodyLabel sizeToFit] within -viewDidLayoutSubViews then it fixes the label but doesn't fix the layout of any of the sibling views (e.g. the Footer label is stuck way at the bottom of the screen instead of pulled up to just under the body). Any attempts to get the entire view to re-layout its children after calling sizeToFit is ignored. I've also tried sizing the UILabel by calculating height based on font, which results in the same behavior as -sizeToFit (albeit with more code).
Replacing the Body UILabel with a UITextView instead doesn't give me the weird vertical spacing issues but I need to calculate the height of the UITextView manually (using font calculations) and something about resizing the UITextView within the parent UIScrollView makes it so the UIScrollView simply refuses to scroll (as if it doesn't know its contents are too big for its bounds).
So at the moment I'm stuck. Even just an explanation of why UILabel behaves this way on the iPad layout would be helpful.
In case anyone else runs into this same issue using autolayout... I may have been able to solve the same issue by creating a constraint as Coche suggests, but I realized I had a preferredMaxLayoutWidth that was too small set on the uilabel. Once I set an accurate preferredMaxLayoutWidth (the actual width of the label) the spacing on top and bottom disappeared.
The main problem is that the method for auto resizing the text inside your Label is failing because in iPad your Label doesn't have a set width from the beginning, it is calculated on run time and that's the source of that mess. On iPhone, as your Label has a set width (on IB) there is no troubles.
There are two ways for solving the problem:
Having two storyboards : one for iPhone and one for iPad
Doing this will make that your Label knows its width since the beginning and it will just works as on iPhone.
Having just one Storyboard for both iPhone and iPad
You can go around the problem by calculating the size that best fits its text and with that result add a height constraint by code to the Label. For calculating the desiredSize you can calculate the width with this formula: Current View's width - (Leading space + Trailing Space). Here is my code
CGSize desiredSize = [_bodyLabel sizeThatFits:CGSizeMake(self.view.frame.size.width-40, 10)];
NSString *visualContraint = [NSString stringWithFormat:#"V:[_bodyLabel(%.0f)]",desiredSize.height];
[_bodyLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualContraint
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_bodyLabel)]];
objective-c