Where is this vertical spacing coming from in UILabelView? - ios

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

Related

Wrapping text in UITextView in Swift

UPDATE: Looks like the UITextView's width is bigger than screen's width.
It is strange since I created the UITextView using a storyboard and in preview mode it looks like it's width is less than that of the UIViewController.
I have a UITextView, in a UIViewController, that was created using storyboard.
I have similar code to populate the text in the UITextView.
self.taskGroupDescriptionTextView.text = taskGroup.description
self.taskGroupDescriptionTextView.textContainer.lineBreakMode = NSLineBreakMode.ByCharWrapping
I tried different types of lineBreakMode, break by char/word. None of them work.
My UIViewController when run on iOS simulator still has text that extends beyond the screen.
You should set constraints. Here's an example how to do it:
You click on your UITextView so that you can start editing it. At the bottom of the storyboard there are few icons and clicking on the one shown in the image will open up an editor where you can add constraints. You have plenty of options (pin to left, right, top, bottom, fix height or width, aspect ratio,... and even more if you click on a neighbour icon). Keep in mind that you have to set enough constraint so that it will be reasonable for your view controller to calculate the frame size of your view. But no worries, if you'll forget something you'll get an error.

Subview greater than its superview in ios / objective c

I have a view of limited width, lets say it 100.
I have multiple labels of same width inside that view and its text is centre aligned. So that text seems to be center aligned always.
Problem is when I got long text label it shows ellipses at the end of the text but I want to show whole text in one time while keeping superview width unchanged. Is it possible to attain this? If not then show how can I wrap that text if it is larger than its width. I'm new to IOS programming.
There is an issue with UILabel and autolayout. If you using it - check this answer: iOS: multiline uilabel only shows one line with autolayout
Also, check actual size of label and insure clipsToBound of superview is set to NO.

UILabel preferredMaxLayoutWidth and wrapping discrepency between iOS 7 and 8

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

resize superview after subviews change dynamically using autolayout

I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ยด#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.

Fluid UI layout on iPhone

I have an Android app with a UI like this for viewing emails:
I'm trying to port this to iOS and need it to work with iOS 5.0 and above (so can't use auto-layout in iOS 6.0). Hopefully you can tell how the layout should adjust/flow based on the example.
What would be the best way to handle this type of layout? The From and Re lines need to be variable height as shown (actually the To: line as well). The message body needs to be variable height of course.
My only attempt so far has been trying to use UITableViewController with static cells. I am able to get the variable height that way, by using sizeWithFont inside heightForRowAtIndexPath, to return the required height for each row. Using that method I'm having a heck of a time trying to get the style I want (rounded corners and background only for the top part).
So is there a better way? Maybe something that uses Collection View or Container View? On some other screens I need to port I have similar issues, but they have more levels of nesting (rounded blue section inside a white section inside a rounded blue section). Or would I be better off not using IB and building the entire UI in code from just basic label elements and generic views?
The easiest way I can think of is to manually compute for the label frames inside viewDidLayoutSubviews. Here's some pseudo-code:
On creation:
In IB, put all labels as subviews of the blue area. Check that the autoresizing mask of the container sticks to the top, left, and right, as well as have stretchable width. We'll fix the height and the subview frames in code. The message body can be a label or a textview as a separate view.
In viewDidLoad, set the containing view's layer cornerRadius, borderColor, etc. as appropriate.
In viewDidLayoutSubviews:
Time label: Easy. Just set the width to the superview width minus some padding, set the height with sizeWithFont:
For To:, From:, and Re:; call sizeToFit. Get the max width and hold on to that.
To: label: Set the x to 0 and y to the time label's bottom.
Receiver's name label: Set x to the width you got from (2.) and y to same as (3.). Set width to (container width - (2.)) and height with sizeWithFont:.
Do the same steps from (3.) to (4.) for the From: and Re: rows.
Set the blue view height to the frame bottom of the subject label.
Fill the rest of the frame with the body textview/label.
You have to add paddings on your own because sizeToFit and sizeWithFont: won't do that for you. Also, the body UITextView can scroll on it's own, but if you are expecting long subject titles then you should wrap the whole thing in another UIScrollView (or in IB just set the main view's class to UIScrollView)

Resources