AutoLayout uitableviewcell in landscape and on iPad calculating height based on portrait iPhone
Asked Answered
R

2

5

I'm learning / experimenting with autolayout and UITableViewCell's. I asked another question a few days ago to which I answered my own question, I'm still using the same constraints / code. For the full code see here: AutoLayout multiline UILabel cutting off some text .

To cut it short inside heightForRowAtIndexPath I am using an instance of a custom UITableViewCell to calculate the height the row needs to be. This works perfect in portrait, however when I switch to landscape mode, systemLayoutSizeFittingSize is returning the same height for the cell as if it was in portrait. I've printed out the frames of the contentView and the labels and nothing seems to be updating.

The result of this is the constraints are forcing the labels to grow leaving a huge amount of whitespace. The labels display in the correct width, in landscape they are laid out as I would expect, If I hardcode the height of the cell it works perfectly.

It looks like this: enter image description here

After hardcoding (what I want it to look like): enter image description here

Even worse I get the same result when running on iPad, even portrait mode, meaning I get the iPhone dimensions. From what I'm seeing it is as though systemLayoutSizeFittingSize has no concept of orientation or device for that matter.

I've tried faking the frame the cell should be, tried rotating the cell, calling layoutSubviews, reloading the tableView on orientation change and nothing seems to affect it.

Have I missed something basic ?

Refreshment answered 6/5, 2014 at 13:47 Comment(3)
Which code from your previous question are you using? From which answer?Roll
@Roll the accepted answer, setting preferredMaxWidth in the UILabel subclass, it saved so much code else whereRefreshment
@SimonMcLoughlin give you +1 for this question.Chatty
A
13

@rdelmar has the right approach. You definitely need to reset the preferredMaxLayoutWidth on each label before you call systemLayoutSizeFittingSize on the contentView. I also use a UILabel subclass with the layoutSubviews method as he demonstrates.

The main downside to an autolayout approach like this is the overhead. We're effectively running autolayout three times for each cell that will be displayed: Once to prepare the sizing cell for systemLayoutSizeFittingSize (size it and set preferredMaxLayoutWidth on each sublabel), again with the call to systemLayoutSizeFittingSize, and again on the actual cell we return from cellForRowAtIndexPath.

Why do we need/want the first autolayout pass? Without it we don't know what width to set our child labels preferredMaxLayoutWidth values to. We could hardcode the values as in @rdelmars example (which is fine) but it's more fragile if you change your cell layout or have lots of cell types to deal with.

If the main issue is recalculating on orientation change then the code below can likely be optimized to run the first layout pass just once per orientation change.

Here's the pattern I use, which negates the need to manipulate the cell controls in the view controller. It's more encapsulated but arguably more expensive.

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // assumes all cells are of the same type!
    static UITableViewCell* cell;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        cell = [tableView dequeueReusableCellWithIdentifier: @"label_cell"];
    });

    // size the cell for the current orientation.  assume's we're full screen width:
    cell.frame = CGRectMake(0, 0, tableView.bounds.size.width, cell.frame.size.height );

    // perform a cell layout - this runs autolayout and also updates any preferredMaxLayoutWidths via layoutSubviews in our subclassed UILabels
    [cell layoutIfNeeded];

    // finally calculate the required height:
    CGSize s = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];

    return s.height + 1; // +1 because the contentView is 1pt shorter than the cell itself when there's a separator.  If no separator you shouldn't need +1
}

along with:

@interface TSLabel : UILabel
@end

@implementation TSLabel

- (void)layoutSubviews
{
    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
    [super layoutSubviews];
}

@end
Alcohol answered 6/5, 2014 at 23:1 Comment(8)
ah i'm such an idiot. I spent hours playing around with the contentView, didn't think for a minute to set the cell frame instead. This works perfect. A minor efficiency degrade is not a major concern at the minute, we have highly dynamic content, but not a lot of it, the issue is reducing layout code.Refreshment
Can I ask your opinion on something? Long story short, the dev's i'm working with hate with a passion IB and have never tried autoLayout. They do everything programmatically and in opinion its biting us in the ass regularly with missing deadlines and horrible code. What is your opinion on autoLayout? do you think its a great step forward or too hard to deal with? taking into account a full app who's content comes from the server and text sizes can change fairly drasticallyRefreshment
Ah, thanks, this same problem has been bedeviling me as well. I had gotten it to work with single multi-line labels using a previous answer of yours, but I was getting inconsistent results with multiple multi-line labels -- setting the frame of the cell was the key missing point. BTW, I don't think you need the call to setNeedsLayout since layoutIfNeeded forces an immediate layout by itself.Roll
@SimonMcLoughlin - I'm an autolayout fan in general but sometimes I pine for the ability to just setFrame:. That's the biggest downside I see is that you can't mix and match. As for storyboards, they're great until you have to merge :(Alcohol
@Alcohol Yes on / off at a storyboard level is a bit irritating alright, but for the most part what we would be using it for, there would be very little places that we could just set a frame, the data will be dynamic, peoples names, bills, usage etc. So That might not affect us. On the merge i've previously worked by myself so never been down this road, i've heard mixed stories about it being ok / awful, guess i'll just have to practise with it and see, this idea of "mini" storyboards to get around that might help us and give us the reusability we want. Thanks for the opinionsRefreshment
@Roll - ah yes; the setNeedsLayout is not needed here! I edited it out.Alcohol
Is it possible to do something if my xib contains some size class specific logic? Dequeued cell we have no info about real size classes, so size class logic won't be used. Is it somehow possible to enforce systemLayoutSizeFittingSize to use specific size class ?Feature
@Alcohol hats off ...you save lot of time..if it was possible then want to give you +100000...thank you so muchChatty
R
3

I'm still trying to figure out how to make systemLayoutSizeFittingSize work correctly, especially when I have multiple multi-line labels. I have one test app that seems to work, but why it works, is still a bit of a mystery. So, I have a subclassed label that has the same code that you're using,

- (void)layoutSubviews {
    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
    [super layoutSubviews];
}

In heightForRowAtIndexPath;, I'm also setting the preferredMaxWidth (using some hard coded numbers based on the constraints). If I remove either of these calls to preferredMaxWidth, it doesn't work properly. Removing the ones in heightForRowAtIndexPath gives me extra space in my labels, similar to what you're seeing.

- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath {
    static RDCell *sizingCell;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sizingCell = (RDCell*)[tableView dequeueReusableCellWithIdentifier: @"Cell"];
    });
    sizingCell.label.text = self.theData[indexPath.row];
    sizingCell.bottomLabel.text = self.theData2[indexPath.row];
    sizingCell.label.preferredMaxLayoutWidth = tableView.bounds.size.width - 40; // the 40 comes from the 20 point spacing constraint to each side of the cell setup in the storyboard
    sizingCell.bottomLabel.preferredMaxLayoutWidth = tableView.bounds.size.width - 40;
    return [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize].height + 2; // the calculation seems to be low by 1 (per label), so add 2 for my 2 labels
}

In this test project, my custom cell has two labels arranged over top of each other. I know this isn't an ideal solution, I'm still testing to see why this works, and why it doesn't with just one of the uses of preferredMaxWidth. In preliminary tests, it looks like it might be a timing issue, as to when the various methods are called at startup and when they're called on rotation.

Roll answered 6/5, 2014 at 16:39 Comment(10)
Thanks for the help but Tom Swift's answer suits me better as a more generic solution. In my second comment to his answer I asked him a question on his opinion of autoLayout, could I get yours too? trying to shake things in work. Not looking for huge long answer, just a brief "I do / don't like it because of X"Refreshment
@SimonMcLoughlin, I use auto layout in all my projects, and I generally like it. I mostly set things up in IB, but I also use categories on UIView to make adding constraints in code easier. Some things, like changing the layout on rotation are far easier using constraints.Roll
Yes this is what i'm starting to see, once I got past this issue i flew along, then I stopped and checked it out, suddenly I have iPhone / iPad and all orientations done. I'm trying to persuade my fellow dev's of the benefits of using storyboards / autoLayout but its not going well. One instead insists that building a parser to read JSON text files to draw the UI is the best approach, which sounds a similar idea to appcelerators titanium (which I hate). This sounds awful to me as I will now have to read, write and modify a JSON structure in a text file for UI work ??? any thoughts on this?Refreshment
@SimonMcLoughlin, That sounds crazy to me. The use of IB, seems to be a point of great contention among developers. I don't really understand why -- it's a tool, and like all tools, you use it where it makes the most sense, and not where it doesn't. Personally, I hate putting lots of UI sizing/placement code in my projects, since I think it adds a lot of clutter that makes it harder to see the more important logic. Auto layout has a bit of a steep learning curve, but once you "get it", it's very helpful.Roll
man, I think I love you ha. My first task was to take over an app they had done and add features, it was all done programmatically with this idea of "componetising" similar pieces of logic, which later lead to this JSON thing. I have never been so lost, frame modifications everywhere, no landscape support, refusal to use UITableViews / cells etc. I literally spend hours a day follow code flows because I can't find what action gets called when X is clicked, its shocking that with tools like this available I have issues like thisRefreshment
Thanks for the help / opinions, I'm asking this question in a few places and trying to build a "Look this is what everyone else thinks, its not just me" argument so I can submit a proposal, I've shown the stuff to the technical managers and they seem to agree with me so far, so fingers crossed. thanks againRefreshment
@SimonMcLoughlin It seems to me that these people don't really understand what they're doing. Rather than delve into the APIs and really get to grips with how they work (UITableViews, autolayout etc) they'd rather just re-invent the wheel and create their own paradigm due to laziness. However, if I was in your position I wouldn't be "submitting proposals" to technical managers - unless you want to alienate your colleagues.Nereus
@ChrisHarrison thanks for the advice. I agree with you that it would alienate them. However I wasn't really left with any alternative. I work in a different building and they frequently do these projects in secret until they are working to some extent. After seeing this one I wrote up a doc of 12 pages showing massive issues in the proposal and spoke to the lead. He wasn't able to answer more than 50% of the issues and the discussion was left at he and the other iOS developer liked this approach. Wasn't left with much choice after this than to explain the issues to the tech managersRefreshment
@ChrisHarrison its a mute point now anyway. We aren't using that approach any more and were investigating the best solution at the minuteRefreshment
@SimonMcLoughlin I too inherited an iOS project that was written using hard coded views all over the place with no auto-layout. So I feel ya. I'm now in the process of making "version 2.0" of the app from scratch all based on autolayout. It took me a year of struggling along with a huge spaghetti mess of a code-base to finally convince the boss it was worth investing the time to re-write.Nereus

© 2022 - 2024 — McMap. All rights reserved.