UITextView with autolayout is wordwrapping too soon - ios

I'm trying to set up a UITextView with autolayout rules inside a UICollectionViewCell.
I have constraints on the leading and trailing side of it, such that:
Leading is greater than or equal to 1:8 the screen's width and trailing is equal to the screen's width minus 15px.
This work well and achieves my desired behavior (keeping the text view as small as possible until it exceeds my maximum line length, then it will wordwrap) most of the time. However, for certain strings/string lengths it screws up and truncates way too early.
An example:
If I set the UITextView's text to: "http://www.billywitchdoctor.com" it all prints out on one line no problem.
However, if I set it to just: www.billywitchdoctor.com the "m" gets knocked off to the next line somehow, so it renders like:
www.billywitchdoctor.co
m
even though there is plenty of space left for the bubble to stretch width-wise. I've tried setting the Compression Resistance Priority to 1000 for the UITextView and reducing hugging but I can't seem to do anything to remove this issue so long as I have these two constraints on the bubble (leading >= 1:8 cell trailing and trailing == cell trailing.)
I know I can probably programmatically fix this issue for each cell by calculating and specifying the width myself, but I'd rather use auto layout if possible.

Try invalidating the layout whenever the text changes.
- (IBAction)textViewDidChange:(UITextView*)textView {
[textView invalidateIntrinsicContentSize];
}

Related

In a UITableViewCell used with autosizing, what is missing from the vertical constraints to make the height as small as possible?

I'm trying to use dynamic heights in a UITableView with a specific cell layout. Consider the following illustrative representation of that layout:
I have the horizontal constraints working properly (15px from both edges, 15px between them, equal widths) but I'm struggling with the vertical constraints. Here are the vertical requirements for this layout:
The vertical intrinsic content size of both the green and blue rectangles are based on external data which is passed to the cell at the time of creation.
Both rectangles are vertically centered within their superview
There will always be a minimum space of 15px between the top/bottom edges of the rectangles and the respective edges on the superview. In other words, whichever one is taller dictates the height of the superview (i.e. the cell)
To that end, here's what I have constraint-wise so far:
Vertical center constraints for both rectangles
Height constraints of the rectangles equal to or less than the height of the superview minus 30 (i.e. if the rectangle's height is 40, the superview must be a minimum of 70. This theoretically achieves the same effect as setting separate top and bottom '>= 15' constraints while using two less.)
Vertical content Hugging on the superview set to 'required' (i.e. 1000)
The third point is because the second points together only define the minimum height for the superview (yellow), but not a maximum. In theory, if it had a height of 10,000 it would still satisfy those constraints.
My thought is setting its content hugging to 'required' would make the superview as short as possible without violating the other constraints, thus at all times, either the green rectangle or the blue rectangle would be 15 px from the edge depending on whichever was taller. However, the height still seems to be 'stretched out' as seen here...
Note: The views on the inside are properly vertically centered and correctly maintain a minimum distance from the top/bottom edges. The problem I'm trying to solve is restricting the height of the superview to be as small as possible.
It doesn't appear that I'm getting any ambiguous constraint messages (I don't see anything in the logs, but I believe I should be because again <= constraints aren't enough on their own, so I'm not sure exactly how to use the tools to debug this, or to find out which constraint is driving the height.
So, can anyone help?
P.S. To ensure it wasn't something external to the cell, like forgetting to configure auto-heights for the UITableView, I removed the two rectangles and replaced them with a simple multi-line label pinned to all four edges. When I ran it with that, the cell properly shrank in size as expected. I bring that up to hopefully stave off answers suggesting that's potentially the problem.
Reading the requirements you provided I have added constraints shown below:
For demonstration purpose I have used a simple view container instead of a cell. Since inner boxes will have intrinsic content size derived externally, I have taken labels to mimic that.
To reiterate, constraints added are:
Horizontal
Container view(orange) has leading and trailing constraints with the super view.
Inner views has leading, trailing constraints with 15points of space.
Both labels have leading and trailing constraints with 9 points.
Both inner views have equal width constriant.
Vertical
Container view is vertically in center.
Both inner views have vertically center constraint applied.
Both inner views have top and bottom constraints with >= 15 condition.
Both inner labels have top and bottom constraints with their super views.
I set the no. of lines property of both labels to zero so that they can grow at run time.
At runtime I changed the text of one of the label and the box and container grew accordingly.
To refresh your cell height implement heightForRow method and provide the latest height. A typical snippet will look something like this (assuming you have already initialized the cell):
cell.needsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
return height
Hope this will help.
Ok, so I was going in the right direction with the content hugging, but the correct way to handle this was to specify a height constraint on the yellow view of 0 and with a low priority. I used 100 to be even lower than the default 250. When I did that, the solver tries to get as close to zero as it can while still respecting the other constraints, therefore 'hugging' the content. Still don't know why content hugging on its own didn't work, but that addressed the issue.

iOS autolayout disregarding "trailing space" constraint, using width/content size instead?

I have a UILabel (the subtitle) that I want to have a static X origin, but extend to the edge of its nearest neighbor. There's a button ("Visit Link") that is optionally removed from the superview at runtime if not needed. The constraint from the label to the button has a priority of 1000, and the constraint from the label to the superview container has a priority of 250:
However, when I run the application removing the button (via .removeFromSuperview() in the viewDidLoad method), via the view debugging I see that the content size is setting the width of the label, taking priority over the constraint I have set.
I expect the label to extend to the edge of the view, but as you can see, the constraint is greyed out - I assume trumped by the (content size) constraint instead:
Does the (content size) constraint have a higher priority than my Trailing Space to: Superview constraint? And how can I change it, since it's not a constraint I've even defined?
When you remove the button from the view hierarchy, that also removes any constraints involving the button. So, all that's left is the trailing constraint to the superview at priority 250.
The label has an intrinsic width based on its content. That means that its horizontal content hugging and compression resistance priorities come into play. Its content hugging priority is 251.
That means that it's more important to the auto layout system that the view's width be no larger than necessary than it is to keep its trailing edge at 8 points from the superview's trailing edge.
You should probably increase the priority of the trailing constraint. You want it to be less than the trailing constraint to the button so that, in the case where the button is present, it doesn't conflict. You also want it less than the button's compression resistance priority, so that the button doesn't get squished to allow the label to be 8 points from the superview. But, other than that, you want it to be as high as possible. (In the hypothetical case where you simply got rid of the possibility of there being a button, you would normally make that trailing constraint required, right? So, it should be as close as possible to required without causing undesirable side effects.)
If you're targeting deployment to iOS 9.0 or later, you should consider using a UIStackView for this layout. It will take care of some things for you, like adding or removing the appropriate constraints when the button is hidden or shown.
The (content size) constraint, automatically installed by the system at runtime, seems to have a priority somewhere between 250 and 750. When I use 250 or 251 for my Trailing Space constraint, it does not work.
However, bumping the priority of my Trailing Space constraint up to the Xcode-titled High priority of 750, allows it to take precedence. So the defaults for the width of UILabel seem to fall "somewhere in the middle."
Autolayout, you silly.
From the second screenshot, it looks like everything is working as it is supposed to.
Two things I'd like to mention:
Always try to set up your layout so you have as few constraints as possible. Each constraint adds complexity.
Yes, content size of the label does indeed have a higher priority. That's why the label is not resized to the right-most edge, which is good. What you are seeing is the label's intrinsic size which means that UIKit knows how big the label is supposed to be drawn, depending on font, text, etc.
To make this layout a bit more robust, I'd change the Trailing space of the label so that it has priority 1000 but is >= 0.
This way, the layout will be valid with or without the Visit Link button and the content size of the label (it's intrinsic size) will resize it to whatever length it needs to be, but no more than the right edge of its superview.
Hope this helps!
UPDATE: Wrote a quick post on why this content size is appearing and why you should use it to your advantage.

UIScrollView with multiple multi-line labels and AutoLayout?

Is there a way to achieve this? I have tried literally everything and nothing has worked for me yet.
So basically what i want to do is the following: I have a scroll view with some labels in it. All the labels get their text from a server and I have set their number of lines to 0 so that they change their height according to the amount of text. However, this does not affect the scrollview content size(even though my labels have constraints set up to the bottom,top,leading and trailing of the scrollview) and the labels go off screen and I am unable to scroll down. Can someone point me in the right direction to how I would set up my constraints, my view hierarchy and etc?
Any help is much appreciated! :)
Late, but this solved it for me:
Set leading (I have a 32pt inset), trailing and top constraints. The trailing will not actually seemingly do anything..
Make the trailing Greater Than or Equal to avoid localization alert.
Finally, add a new Equal Width constraint to the label matching the scrollview. Use the constant to subtract the required padding (I used 64 due to mirror my leading inset).
And voilĂ ! The Label will align correctly both in IB and in-app.
In Scrollview the last view's bottom constraint is so important. You should set its priority to 250 and put it to Greater than or equal.
Remember you should only change the bottom constraint of the last view, which in my case it's the continue button.
I would consider using UITableView instead, it has several benefits:
It allows for reuse of cells, if all the cells look the same
It manages recycling of cells when the number of values you're getting from the server increases (decreases memory pressure when number of cells becomes substantial)
It allows for more flexibility with the content (it's quite often for design to change last second or to evolve over the course of the project)
Most importantly, UITableView support auto sizing cells (as of iOS8), you need to specify the constraints between the label and the borders of the cell
There are several resources to start with:
http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
https://www.captechconsulting.com/blogs/ios-8-tutorial-series-auto-sizing-table-cells
http://www.appcoda.com/self-sizing-cells/
Use a container view in a scrollView
Add constraints to superview (leading, trailing, top, bottom, height,width)
Make IBOutlet of constraints that you are going to update.
Add you all labels inside that view.
Update constrains/frame of your label so that it fits the text.
How much you increase the label height you should increase the container height too.
If the label count is not fixed use custom label class to add subview.
Perhaps you should need to understand how ScrollView works in Storyboard with autolayout.

Allow UILabel to grow dynamically using auto layout

I have a label which is going to contain a big description. I want the label to continue growing on new lines. In the image, its the label which starts with event_venue.....
The even_venue.. label has 3 constraints for now:
Vertical space with eventt_title
a horizantal space with the leading of the superview
a width constraints which defines that the label width is always less than the superview.width.
What I want to acheive is to make the event_venue.width less than superview.width, but if it has more text, it should display in new lines. Is this possible using autolayout?
This are possible steps which can create expandable UILabel
Set layouts for UILabel fixing its position
Set number of lines = 0
Set content vertical compression resistance to 1000 (this will allow text to push the label)
Since you want UILabel to expand you cannot give it fixed height constraint or its parent fixed height constraint. Sometimes depending upon condition giving height constraint is necessary to avoid error then you need to set its priority lower than vertical compression resistance
Yes, this totally is possible. I see answers here that are close to solution but not complete. Here is a solution which works with auto layout in Storyboard, no coding of sizeToFit or anything. Your modified steps would be:
Vertical space with eventt_title
A horizontal space with the leading of the superview
A horizontal space with the trailing of the superview
Set UILabel's Line Breaks as Word Wrap.
Set UILabel's lines property as 0.
I have solved a similar problem. I had to make a label that had a variable amount of text. Here's what I did:
In the storyboard, place your label with the origin where you want it.
In the Attributes Inspector, "Label" section, set the Line Breaks = Word Wrap
Fill the label with random placeholder text to the maximum shape you want. For example, if you wanted to fill the whole width and have room for a maximum of three lines of text, you could do:
abcdefghijklmnopqrstu
abcdefghijklmnopqrstu
abcdefghijklmnopqrstu
In the code, set the text of the label using setText:
[self.myLabel setText:#"MyLabelText"];
This did it for me. Your situation may be a little different in that I wasn't changing the width of the superview and it sounds like you might be. But if the width constraint is set on the label then I would expect this to work in your case, too.
I had a similar question about label resizing, and the answer that I found that was useful to me is here: UILabel Auto Size Label to Fit Text. This is a good starting source for code on how to resize your label programmatically.
I would recommend that you also add a horizontal trailing auto layout constraint from the label to the edge of the superview. Do that and you can then get rid of your current width constraint.
AutoLayout facilitate you for orientation purpose. I don think it will give you automatic expansion. You have to define label with width and height completely, otherwise you will see dots at the end of label. So you may use UITextView expanding it all over the screen. And set textView.backgroundcolot = clearColor.

Label's height issue in tableView with auto layout

I use autolayout. I am displaying 2 labels in custom UITableViewCell. Label1 is above Label2. Their text is dynamic.
The issue is the height of one of the labels when displayed is larger than its text.
I tried changing their Content Hugging Priority.
So what happens is, if that priority is same or Label1's priority is higher, then Label1 is having exact height to fit its text but Label2 has larger hight than required. And when Label2's hugging priority is higher than issue is with Label1's height.
Any idea how to solve it?
It looks like you are expecting your cells to auto-size based on auto-layout's constraint information, but UITableView sizes it's cells using frames/autosizing masks. If this is the expectation and you aren't autosizing the cells like in this question, then your labels are going to either force-clip themselves in order to fit inside the cells with the sizes they were given from the table view or grow to satisfy all of the margin-constraints.
Since the content hugging and content compression resistance priority values are less than required (less than 1000), they are considered "optional" and will be satisfied as close as they can be without violating any of the other required constraints. This is why your label begins growing (or clipping itself).
This can be solved in a couple of ways off the top of my head:
If you don't care about the cell having a variable height and are fine with the labels migrating toward the top of the cell, then make the constraint that pins the bottom label to the lower edge of the superview be non-required. More specifically, make that constraint have a priority lower than the vertical contentHuggingPriority for both of the labels. This way the content hugging priority constraints will take precedence over the lower constraint.
Make your cells auto-size themselves (using auto layout or otherwise) so that the system never has to consider the vertical contentHuggingPriority of each label 'optional'.
I solved this after experimenting with lot of things. The only thing I had to do is to set horizontal and vertical content compression resistance priority to required.i.e. 1000.
I did this for all labels because I don't want any of the labels to trim their content.
One more thing which is too much important is Getting Right Height Of Cell. If there is even 1pt of error in calculating custom cell's height it will not be displayed as expected.
Hint :
If height of any view is greater than expected then possibly calculated height of cell is greater than what is actually required.
If any of views is shrinking vertically or not displaying whole content then possibly calculated height of cell is lesser than what is actually required.
Yoy can test if height is wrong by adding/removing constant value to height (variable) you calculate for cell.

Resources