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

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:

Related

Create UITableViewCell constraints to match with built-in cells and avoid "fixed width constraints my cause clipping."

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:

UILabel scales only for certain text lengths

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?

Embedding three UILabels in a horizontal UIStackView gives different results in a table view

For a week I have been struggling with a 'simple' piece of layout in a storyboard. I want three labels which all have numberOfLines set to two.
The UIStackView has some constraints to position it in the table cell. The two left labels have a width constraint set to <= 100 to make sure they don't stretch out too far. I have been playing a lot with the content hugging and compression resistance and with things like setNeedsLayout or layoutIfNeeded. You can see the problem in the screenshot I added. There is barely any code written in the ViewController.
When you check out the test project I added and run it on the simulator you will notice when scrolling up and down the cells will all start to look the same and the text is no longer truncated. That's exactly what I want.
Here is a link to the test project I am working in.
In Prototype Cell set horizontal and vertical Content Hugging Priority to 1000 and the same for Content Compression Resistance Priority to 1000 (Left Label and Middle Label) and the result is what you are probably looking for, make Left and Middle labels as small as they can be and the right one to fill the gap...

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.

Custom/Dynamic label width using auto layout

I have a table cell on which I presently have a UILabel say labelA whose height is dynamic i.e. based on the content. The label width is 290px. I wish to add another label next to labelA whose name is say labelB. The table cell and labelA looks as per below:
Question 1 - Here the height is expanding and I have 2 rows to display the entire text here. But it looks like width for the 1st and 2nd row is same as 290. Is there a way I can stop the width of the label where my text ends i.e. in this case the width of my label for 1st row should be 290 but for 2nd row it should end after the last work "Paul" so that I can start my next label labelB from there?
Question 2 - Also, while creating the xib, I am placing both my labels on the same row. If I set a constraint between both the labels, can labelA push labelB down to another row when it expands?
My cell and labels finally should look as below where the label with purple background is labelA and rect with orange border and white solid color inside is labelB.
Please let me know if this is possible? I am still a noob in iOS and auto layout.
To answer Question 1, UILabel will always draw text into a rectangular frame. You can't have one UILabel draw two lines of text the way you're looking for. You could use two UILabels, one for each line, but you'd have to do some manual calculations to figure out how much text will fit in the first label, and then what text to "overflow" to the second (boundingRectWithSize:options:attributes:context: would be your friend for this).
To answer Question 2, you should pin the bottom edge of labelB to the bottom edge of labelA. Let the height of each label be determined by the intrinsic content size. Assuming the font is the same, this should result in the result you want. (You can also try aligning the two labels' baselines, but I can't recall how that behaves for multiline labels.)
By the way, you should look into iOS 7's Text Kit. I personally haven't had a chance to dive into it in much depth yet, but it's very powerful and you might find a better way to do what you're after here.

Resources