UILabel scales only for certain text lengths - ios

I have a custom UIView containing a UILabel and a UISwitch. The switch and the label are constrained to be next to each other with their center-y equal and their sides matching the sides of the parent (with a margin). The label is constrained to connect with the top and bottom of its parent (again, plus margin) with its number of lines set to 0 so it can scale as large as it needs to be to fit all the text, using word wrapping as the line break mode. I have a bunch of these custom views below each other in a vertical UIStackView with alignment and distribution set to fill. The stack view itself is positioned inside a scroll view which is constrained to all sides of the root view (which scrolls as expected). Here's a screenshot of the situation that also shows my problem:
I've added a red background to the label in the custom view to show its boundaries. Separator views are added between the custom views. The problem becomes apparent in the second custom view in this screenshot. Its label text is actually WordOne WordTwo WordThree WordFour, but for whatever reason the text is wrapped without the label / parent increasing their size to contain it. Adding an extra word, as in the view below it, suddenly does scale the label to show all of its lines. I've set compression resistance to UILayoutPriorityRequired for both the label and the parent, to no avail. I have a workaround involving a layoutSubviews override with a manual calculation of the label size, but I'd prefer both understanding what's going on and of course avoiding having to use a hack. Can anybody shed some light on this behavior?

Related

UITableViewCell expanding animation with self-sizing labels, visual animation problem

I've got a problem with self-sizing and animatable table view cells, with the animation part only.
The issue is that when the cell expands, 2 of the labels are overlapped as the other items expand. If you look at the gif below, the top label "370 Base Axle Ratio" becomes overlapped by the middle elements. Those middle elements slide down as a group, which is also undesirable, and if you look closely, the "0" under the words in the middle "Original Production" is also overlapped and it expands or is revealed incorrectly.
The top bold label "370 Base Axle", is set to be 0 lines of text so it can expand to fit multiple lines. The center group of labels is anchored to the bottom of that label. The bottom long text label is anchored to the bottom of the middle "0" label.
If I anchor the middle group of items to the top of the view at a fixed amount, then the expansion works without the overlap effect, but of course this breaks the dynamic nature of having multiple lines of text in that top label.
This layout is built with auto-layout, but is not using stack views (and is not my preferred solution at this time).
No specific code is used to set any layout sizes, only using this in the tableViewController after I add or remove the text in the bottom label:
tableView.beginUpdates()
tableView.endUpdates()
How would I fix this so the ONLY element that would expand is the bottom most label with the long amount of text, but still have a variable label height at the top?
TL;DR: Labels with multiple lines of text breaks animation when other multi-line labels are animated in a "show more" style of expanding table cell.
You can almost fix your issues with very few changes.
Two important things:
For all labels, set both Hugging and Compression Resistance to 1000 (Required)
Make sure you have a complete vertical constraint chain.
Couple drawbacks to your method of setting / clearing the text of the label though. The expand animation will work well, but the collapse animation will look as it does in your original gif --- the text disappears instead of being covered. Also, you have extra spacing at the bottom.
To get around that, you can set a constraint from the bottom of the "description" label - as you likely have it now - to the bottom of the content view... this will be the "expanded" constraint, AND add another constraint from the bottom of the "center 0" label to the bottom of the content view... this will be the "collapsed" constraint.
Those constraints will conflict at first... so change the Priority of the one of those constraints (doesn't matter which) to 750 (Default High) and the other constraint to 250 (Default Low).
When your app is running, you would swap the priorities based on whether you want the cell expanded or collapsed.
I put together a sample app using your layout - You can download it here: https://github.com/DonMag/Maverick
I use background colors to make it easy to see the frames. There is a line in cellForRowAt that can be un-commented to clear the colors.
The result using your set / clear text approach:
and using the Constraints method:

Swift Storyboard - Centring an Icon and multi-line text horizontally

I am trying to create an Android-esque Snackbar. All the behaviour is correct but I am left with a layout issue. I have actually broken this out into a test app to simplify things a bit.
The bar is pinned to the bottom of the view and contains an Icon (UIImageView) along with some text (UILabel). This text can be up to 2 lines max.
For example, it should be able to exand out like this:-
However to further complicate things, i need it so that the icon and text are centred within the red bar and then spread out from the centre until such point that it needs to wrap. Note that there is a padding to the start of the icon and end of the text to prevent it touching the sides. An example of centred content would be..
I haven't been able to get the correct layout. I think the issue has been trying to centre the two items whilst simultaneously conforming to a width that doesn't exceed the edge bounds, causing a constraint conflict. I have also tried embedding the icon and label in a horizontal stack view but couldn't seem to find the correct fill option whilst centring everything.
I have even tried using NSAttributed string and adding the image to the text itself but when the text wrapped, the icon was being resized and/or misplaced.
As you can see, i have been doing this in Storyboard but i am more than happy to do this programmatically if it serves this purpose better.
Created this Demo for reference , it's a simple task of making a nested view inside the red view with a centerX constraint of priority 1000 and a leading constraint with 999 priority
Now you have this effect

How to constrain a view to a UITextView's first baseline?

I'm desperately trying to constrain the first baselines of a UILabel and a UITextView in Interface Builder. From my understanding this should be quite easy by simply adding the following constraint (Pseudo-Code):
label.firstBaseline = textView.firstBaseline
However, depending on the other constraints and some view settings I get really awkward results as shown in the following screenshots.
1st Case: Disable scrolling & Align the top edges
This is only for showing the general view setup. I have disabled scrolling for the textView to avoid ambiguity. (Otherwise the textView cannot calculate its own height from its containing text.)
2nd Case: Disable scrolling & align the first baselines
Same setup as in the 1st case with the only difference that I have removed the constraint that aligns the top edges of the views and added a constraint that aligns the first baselines of the views instead.
For some reason, the label "takes off" well beyond the bounds of its superview and ends up at a y position of 7764. No idea why.
3rd Case: Constrain height & align the first baselines
Now I have scrolling enabled for the textView and constrained its height instead.
Obviously the label's first baseline gets aligned with the textViews top edge even though the constrained says to align both their first baselines. I read in the Apple docs that the firstBaseline parameter returns a view's top edge if that view doesn't have a baseline. So it seems that the textView doesn't have a first baseline in this scenario.
Does a UITextView in general have no firstBaseline?
And if so: Why? After all, it's a view that draws text and thus this property should be set.
Any thoughts on this are appreciated.

Center multiple UILabels on a line

I want to horizontally center multiple UILabels - as a group - on a line in Interface Builder. One the straight view controller i could not figure out how to do this. I read comments about place the UILabels in a View and then centering the view in the view controller. When I tried this, overtime I said to update frames in the interface builder, the View would be resized down to nothing. (i.e. its height and width would be set to 0 by IB). I need to know how to get this to work in interface builder.
An example of a line containing multiple labels I want horizontally centered is below. The braces simply indicate the start and end of each label and are not art of the text. The <- 6 -> is meant to indicate the trailing space from label 1 to label 2 is 6
Label 1 Label 2
[Rating: 0.0]<- 6 ->[Distance: 125.34 Kilometers]
Any suggestions would be greatly appreciated.
Here is an EXACT example of what I have done and it does not work:
Create a new view controller in IB
Place a page label at the top of the page centered horizontally and aligned to the top of the layout guide.
Add a view with the following constraints on the page:
Now add two labels to the view with the following constraints:
Once this is done I get the following error and updating the frames will cause it to be give a height and width of 0
Here is what the page looks like in IB:
I need the height and width of the view to size automatically so that I have put a multiline label in the view and have all the contents of the view treated as a group and centered on the page.
Any suggestions would be greatly appreciated.
Here is a snapshot of a working set of all the constraints on an abbreviated layout that produces an always-centered view that automatically resizes with the child views (i.e., as the content of any label changes, the view grows or shrinks around it).
Important to your solution, the view has neither a height nor width constraint; its size is constrained entirely by its descendant constraints.
The key is that every component has a direct or indirect constraint from which its size and position are specified or can be inferred. For example, in order for the view to infer its width, the child labels must have a leading space constraint on the first label, a trailing space constraint on the last label, and a horizontal space constraint between interior labels. Those constraints plus the contents of the labels allow the width of the view to be inferred ... and force the view to dynamically conform to that width.
The same applies for the view's height. For example, you can specify the top and bottom space of just one label, or all of them. If just one, the remaining labels can be vertically aligned with it (see "Align Center Y to: Label1" in the screenshot).

Auto Layout without explicit width constraints and with greater-or-equal trailing space misbehaving

I've got a scroll view contained directly inside the content view of a view controller, at full size in both dimensions. The top, bottom, leading and trailing space constraints from the scroll view to its superview (horizontal) and the layout guides (vertical) are all set to 0. The VC is eventually meant to be nested as a child view controller in one or two places. I'm using a Storyboard.
I've placed a number of elements inside the scroll view and constrained them to it, but I'm seeing various kinds of strange behavior. Below is a screenshot with all the subviews of the scroll view selected to display their constraints. The scroll view's four constraints to the top level view are not visible in it. The view controller has been set to Freeform size, with its top level view (and, accordingly, the scroll view's content view) 616 pts high, guaranteeing that scrolling will be necessary at runtime.
Before analyzing the screenshot, here's a list of things that I'm trying to achieve.
The vertical spacing between elements is set by the designer and fixed. (BTW, none of the vertical constraints, text styles etc. in this wireframe are final yet; the whole image is for illustrative purposes only.)
All the labels (except the topmost one) should start at their intrinsic size, expand up to the width of the scroll view (minus the standard HIG horizontal space of 20 pts on both sides).
Buttons are unlikely to be much bigger than this, but in case of localization surprises, we want them to behave just like the labels. (There's an extra vertical ≥ constraint on "Another Button"; it's irrelevant to this question.)
The web view has a fixed height, and its width should be determined by the width of the scroll view; standard 20 pt horizontal space on both sides.
The text views have a minimum height (67 pts here), but they should expand vertically if the contained text is too big to fit. None of them are editable or scrollable. Like the web view, they're horizontally spaced the standard 20 pts apart from the leading and trailing edges.
As you can see, none of the elements have explicit width constraints. The whole thing relies on the leading and trailing space constraints between the elements and the scroll view. The layout, in my mind, would somewhat gracefully work on hypothetical wider-than-320 pt iPhones of the future without changes to the constraints. It would also work after rotating to landscape orientation (it might look a bit silly, but it would work).
I'll go through the points step by step, referring to the screenshot where necessary.
1: This works, nothing out of the ordinary here.
2: The leading constraints of the labels are all simple Equal 20 pt standard spaces. The trailing constraints are Greater Than or Equal 20 pt standard, ostensibly to allow them to grow to be scrollView.frame.size.width-40 wide, but no wider.
3: Same as 2.
4 and 5: Here's where it gets interesting. The web view and the text views are all listed as Misplaced Views, with IB saying their frames will be different at runtime. The orange dashed borders denoting the correct frames only reach horizontally as far as the longest element with a Greater Than Or Equal trailing constraint; here, it's "A Button With A Long Title", whose right edge is where the dashed border edges end.
Constructing this set of views and their constraints, I expected to have some trouble. I knew it would be tricky to have UITextViews that grow vertically taller than the ≥ 67 height defined here, perhaps only possible through code. Getting the labels and buttons to work as specified above through IB alone seemed a bit iffy, too.
What I didn't expect was the web and text views' reported correct frame being only as wide as the widest label or button. It seems that with this setup, the scroll view won't actually be 320 pts wide, but rather only as wide as necessary to fit the longest element and its spacing, and the web and text views are expected to comply. Given that the scroll view is firmly constrained on all sides to the top level view, which is set to be 320 pts wide, I have no idea why this is. SOMETHING must obviously define the initial width of the scroll view, but why aren't the constraints I've made from the scroll view to the top level view doing that?
Given the specifications above for this set of views, what do I need to change to make it happen?
This case demonstrates the fact that I truly do not properly understand Auto Layout yet, and I hope that the answers will enlighten me about many of its crucial aspects.
With respect to Xcode's warnings about misplaced views, select the view controller in storyboard, tap the "Resolve Auto Layout Issues" button in the lower-right-hand corner of the canvas (it looks like a Tie Fighter from Star Wars), then select "Update All Frames in View Controller". This forces all of your views to reflect their constraints.
Using Auto Layout with UIScrollView is a different animal; so much so that Apple felt it necessary to release a Technical Note on the issue: https://developer.apple.com/library/ios/technotes/tn2154/_index.html
When you connect all those constraints between subviews and the inside walls of a scroll view, the result is probably not what you expect. When you pin a subview to the sides of a scroll view, you are not in fact determining the subview's position relative to the scroll view. Instead, you are determining the scroll view's contentSize. This is weird.
You are using Apple's so-called pure Auto Layout approach from the Technical Note. With this approach, the subviews' constraints must dictate the scroll view's contentSize.
Let's take just one of your subviews and ignore all the rest, say one of the text views. And let's only think about that text view along the horizontal axis. If you wanted the text view to be constrained by the width of the scroll view without any horizontal scrolling, you would need to install a fixed-width constraint on the text view that was exactly the width of the scroll view's bounds minus the spacers. After doing this, the content size width would be the sum of the left spacer, the width of the text view, and the right spacer.
Unfortunately, you cannot install a constraint that establishes a relationship between the width of the text view and the width of the scroll view's bounds. And that's really too bad.
I don't actually recommend installing a fixed-width constraint on the text view. Instead, I would start over and use Apple's "mixed approach" from the Technical Note.
With the mixed approach, the subviews' constraints don't determine the scroll view's contentSize. Instead, you must explicitly set the scroll view's contentSize and the frame of a container view (i.e., a UIView content view).
Let's go back to that UITextView and the horizontal axis. Using the mixed approach, you could leave the constraints for the text view as they are (i.e., no fixed-width constraint). You could explicitly set the width of the scroll view's contentSize and the width of content view's frame as early as viewDidLoad. You could explicitly set these values to self.view.bounds.size.width because your scroll view hugs the sides of the main view.
To implement the mixed approach, you will have to instantiate the content view (UIView) in code and not set its translatesAutoresizingMaskIntoConstraints property to NO. By extension, you'll probably need to create your constraints for all those subviews in code as well (I don't know of any way around this). The visual formatting strings are tedious and repetitive, but they're actually easier to work with than constraints created in IB when configuring complex layouts (your layout is sufficiently complex).
I used the mixed approach to solve a SO challenge here: https://stackoverflow.com/a/21770179/1239263. Unfortunately, the solution hasn't been vetted yet. It's always possible that I'm nuts and I don't know what I'm talking about.

Resources