Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two multi-line labels #22

Closed
Isuru-Nanayakkara opened this issue Apr 4, 2015 · 5 comments
Closed

Two multi-line labels #22

Isuru-Nanayakkara opened this issue Apr 4, 2015 · 5 comments

Comments

@Isuru-Nanayakkara
Copy link

Hi,

I'm faced with this strange issue. I have a prototype UITableViewCell with two UILabels with both of their no. of lines set to 0 and all the necessary constraints are added .

screen shot 2015-04-04 at 7 35 09 pm

Here are the Content hugging and content compression resistance priorities.

  • Top label

screen shot 2015-04-04 at 7 36 03 pm

  • Bottom label

screen shot 2015-04-04 at 7 37 52 pm

And here is the code.

import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
    }

    private func configureTableView() {
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 90
    }

    private func eventCellAtIndexPath(indexPath: NSIndexPath) -> EventCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("EventCell") as EventCell
        cell.nameLabel.text = "(Monrovia) Brennan, Cathleen Andresson Carlsson"
        cell.addressLabel.text = "176 N, Sunset Pl, Monrovia, CA, United States 91016"

        return cell
    }

    // MARK: - Table view data source
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 10
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        return eventCellAtIndexPath(indexPath)
    }
}

Even though their is no auto layout issue, when I run the app this is how the content is displayed.

1

As you can see the first label is getting cropped!

I faced this same issue a few months back and your answer helped me to resolve it. But doing the same here doesn't seem to work.

private func eventCellAtIndexPath(indexPath: NSIndexPath) -> EventCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("EventCell") as EventCell
    cell.nameLabel.text = "(Monrovia) Brennan, Cathleen Andresson Carlsson"
    cell.addressLabel.text = "176 N, Sunset Pl, Monrovia, CA, United States 91016"

    cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
    cell.addressLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.addressLabel.frame)
    return cell
}

2

Why do you think this is happening?

I uploaded a demo project to my Dropbox.

@smileyborg
Copy link
Owner

Hey @Isuru-Nanayakkara,

Thanks for sharing such great detail about the problem you're seeing and the sample project, it really makes it much easier for me to help! 😃

Anyways, here's what's going on. As we've seen previously (in issue #10 here), there can be issues with the preferredMaxLayoutWidth not being set correctly/automatically when loading a cell from a nib or Storyboard. That is indeed the case here, if you run your sample project and inspect this property on both labels before the cell gets returned from tableView:cellForRowAtIndexPath: you'll see that the value is 0.

Uncommenting the following two lines of code to set the preferredMaxLayoutWidth manually also doesn't work, as you mention:

    cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
    cell.addressLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.addressLabel.frame)

The reason is because when these lines of code execute, we are using the width of each label to set the preferredMaxLayoutWidth -- however, if you check the width of the labels at this point in time, their width is totally different from what it will end up being once the cell is displayed and its subviews have been laid out.

So, we're on the right track, but we need the label widths to be accurate at this point. How do we do that? Here's the final code that makes it all come together:

private func eventCellAtIndexPath(indexPath: NSIndexPath) -> EventCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("EventCell") as EventCell
    cell.nameLabel.text = "(Monrovia) Brennan, Cathleen Andresson Carlsson"
    cell.addressLabel.text = "176 N, Sunset Pl, Monrovia, CA, United States 91016"

    cell.frame = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999)
    cell.contentView.bounds = cell.bounds
    cell.layoutIfNeeded()

    cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
    cell.addressLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.addressLabel.frame)
    return cell
}

OK, so what are we doing here? Well, you'll notice there are 3 new lines of code in the middle of this method. First, we need to set this table view cell's frame so that it matches the actual width of the table view (we're assuming the table view has already been laid out, which is the case for this example). We're basically just making the cell width correct early, since the table view is going to do this eventually.

You'll also notice that I'm using 99999 for the height. What's that about? That is a simple workaround for the problem discussed in detail here, where if your constraints require more vertical space than the current height of the cell's contentView, you get a constraint exception that doesn't actually indicate any real problem.

Next, we make sure that the contentView of the cell has the same size as we just assigned to the cell itself, by setting the contentView's bounds to equal the cell's bounds. This is necessary because all of the auto layout constraints you have created are relative to the contentView, so the contentView must be the correct size in order for them to get solved correctly, but just setting the cell's size manually does not automatically size the contentView to match.

Finally, we force a layout pass on the cell, which will have the auto layout engine solve your constraints and update the frames of all the subviews. Since the cell & contentView now have the same widths they will at runtime in the table view, the label widths will also be correct, which means that the preferredMaxLayoutWidth set to each label will be accurate and will cause the label to wrap at the right time, which of course means the labels' heights will be set correctly when the cell is used in the table view!

Hope that all makes sense. Thanks for bringing this up, this is definitely an Apple bug in UIKit that we have to workaround for now, and as you can see the workarounds change subtly from one version of iOS to another.

@Isuru-Nanayakkara
Copy link
Author

Hi Tyler,

Thank you so much for the very detailed response yourself. The workaround did the trick.

By the way if you could update your answer here, I can accept it back so that it might help someone else too.

Thanks again.

@smileyborg
Copy link
Owner

@Isuru-Nanayakkara Thanks for the suggestion, I have updated the Stack Overflow answer with this info!

@lucascerro
Copy link

This line is golden: "but just setting the cell's size manually does not automatically size the contentView to match". So easy to overlook and not understand why everything's broken...

The whole topic solved my current problems as well as anticipated problems I would have in the future. Thanks!

@aodhol
Copy link

aodhol commented May 3, 2018

@smileyborg Now that you can tell for sure if there's a bug ;) (instantiating from a nib) can you also tell us how this might be solved internally? and whether this is likely to ever get fixed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants