k1th k1th - 1 year ago 99
iOS Question

Resize UITableViewCell asynchronously vs. generated UIView-Encapsulated-Layout-Height

I am trying to resize a table view cell "from the inside", using iOS 8, thus not implementing

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

but using:

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 200;

The cell has constraints top to bottom.

The cell contains (among other things) a UIWebview that knows its size asynchronously after load by asking the scrollview for its height:

CGFloat fittingHeight = self.messageTextWebView.scrollView.contentSize.height;

(this is a variant where the webview is contained within another view and that view resizes accordingly).

Because the whole auto layout process is finished when this happens, I set

[self setNeedsUpdateConstraints];

on the view in the cell which bubbles up to the cell and down again with

In the end, I get the dreaded
Unable to simultaneously satisfy constraints.
. The constraint that breaks everything is

"<NSLayoutConstraint:0x7f8c29d157f0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8c2b063eb0(270)]>"

This constraint is created by the table view when the cell is being calculated for the first time.

It kills my own:

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7fa8a8515b70 V:[UIView:0x7fa8aa0263e0(263)]>

Although I set my constraints' priorities to 1000 as well as compression resistance to 1000, I cannot override the constraint created by the table view.

What's frustrating:
If I replace the web view with a simple multiline label and know all the sizes during the first cell layout, everything works perfectly. It's the asynchronous nature of the web view's loading system that forces me to change the layout after the cell is rendered for the first time.

Now my question:

Is there any way around this? I don't really want to reload the cell. If the cell is the only one on screen, it's not being reused and the whole thing starts over again.
Is this even possible or does the UITableView architecture depend on that outer constraint to render itself correctly?

Just for the fun of it, I set

self.translatesAutoresizingMaskIntoConstraints = NO;

on the cell itself, resulting in a cell that has a size of 0,0 and the constraint:

"<NSLayoutConstraint:0x7f88506663a0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8850648400(0)]>"

but it's still there...

thanks for your help.

BTW: I already read everything there is regarding the matter, including:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Answer Source

After some more research, my conclusion is:

Either use attributed strings that now allow HTML content (since iOS7):

[[NSAttributedString alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
     NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding] 
documentAttributes:nil error:&err];

The HTML parser is ok, limitations are

  • you should never use src tags referencing content on the network. Content is loaded on the main thread. I figure they had to do that to force synchronous rendering.
  • there is no interaction (which is actually a good thing).

Or: A collection view might work but I haven't converted the table view yet - depending on my test results with NSAttributedString I might skip that...


I tried using the fantastic https://github.com/TTTAttributedLabel/TTTAttributedLabel It renders attributed strings with the added bonus of detecting links. Which was everything I needed – almost. It doesn't render tags...

Got back to UITextView which surprisingly works just fine.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download