6

Sample Project: http://cl.ly/1o2K2m2r262q

I have a UITableView with custom cells that have their height auto-calculated from Auto Layout. The custom cells has three labels within it, each positioned with vertical spacing between one another and the content view, and pushed away from the left side.

It works great when I input the data and it loads.

However when I modally present a view controller from the view controller hosting the table view, I notice it completely breaks Auto Layout as I return to the original view controller.

What would cause this? I populate the data into a simple array that acts as the model for the table view's data source, then it's just Auto Layout. It's such a simple project that I'm confused where it would be messing up.

Addition: I appreciate rdelmar's answer, but I simply can't believe that there's not a single app shipped right now that takes advantage of this dynamic cell feature of iOS 8 without making terribly jumpy table views. It would be incredibly noticeable. Someone must have figured out a way to make this work, otherwise they never would have shipped it.

Doug Smith
  • 29,668
  • 57
  • 204
  • 388
  • Make the changes the storyboard suggests to get rid of the red warning arrow, then in `viewWillDisappear`, add `self.tableView.estimatedRowHeight = 166.0`. – gabbler Dec 06 '14 at 03:55
  • @gabbler They're not always of that height though. It still seems to jump back to the wrong scroll position. – Doug Smith Dec 06 '14 at 03:57
  • Possible duplicate of http://stackoverflow.com/questions/18048514/shifting-view-after-displaying-modal-possibly-autolayout-related – fishinear Dec 06 '14 at 14:10
  • @DougSmith, yes it is jumpy, the `contentsize` of `tableView` is changing when it scrolls. When you came back,`contentsize` and`contentOffset` will change between `viewWillAppear` and `viewDidAppear` calls. I think you can manually calculate cell height to fix this. I did a test to save all the cell heights calculated by self sizing cells using `NSUserDefault` in `viewWillDisappear`, when I come back, I use the cell heights retrieved from `NSUserDefault`, and the jumpy behaviour goes away. – gabbler Dec 06 '14 at 16:14

4 Answers4

2

You have several problems. In the storyboard, there is a red warning arrow in the scene list. You shouldn't ignore that. Click on it and make the changes it suggests (do the compression resistance values first, and I think the content hugging one goes away on its own).

In MasterViewController's viewDidLoad, add these two lines that you need for self-sizing cells,

self.tableView.estimatedRowHeight = 120
self.tableView.rowHeight = UITableViewAutomaticDimension // you may not need this one, I think it might be the default now

Finally, I've found that when I make the cell in the storyboard (as opposed to in code), I need to add the following method to the cell class, so the layout happens right away (otherwise it doesn't layout properly until you scroll or rotate),

override func didMoveToSuperview() {
        self.layoutIfNeeded()
    }
rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • It oddly [shows no warnings in my Storyboard](http://i.imgur.com/n79bqIV.png). The rest worked rather well though, but with the implemented suggestions, when I scroll, tap a cell or tap the "Go" button to present the view controller, my position in the scroll view is altered, but only sometimes. Why is that? – Doug Smith Nov 20 '14 at 04:57
  • @DougSmith, I think that has to do with the fact that you're estimating the height of the rows, so they're not exact, and that estimated height is used to set the contentSize of the table view. If you scroll to the bottom of the table view, and then back up to one of the rows, the actual height will be calculated, and that effect will go away (or at least be minimized). – rdelmar Nov 20 '14 at 05:09
  • How am I supposed to perfectly estimate the size of each cell, especially with Dynamic Type and dynamic height depending on the content? A user wouldn't expect to be at one content offset, and when they return be at a different one. There must be something else. – Doug Smith Nov 20 '14 at 13:59
  • @DougSmith, Yes, there is. You can use the old method where you calculate the height of each cell in heightForRowAtIndexPath -- that's all done for each row before the table view is shown. If you need to support iOS 7, you need to do it like that anyway. I've seen some discussion that the problem you're seeing is a bug. – rdelmar Nov 20 '14 at 17:01
  • Currently the automatic height calculation is very buggy. Take it as is or leave it. And open bug reports with Apple so they fix it (maybe) by iOS 10. `;-)` – Léo Natan Dec 05 '14 at 20:11
  • @LeoNatan Should that be the case, I'm sure someone has found a work around. – Doug Smith Dec 06 '14 at 00:55
  • Calculating the height yourself is the best workaround. – Léo Natan Dec 06 '14 at 09:29
  • I recently did something similar. I used (https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8)and (http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout) as guides to accomplish this. I believe they both have the same jumping offset problem that you are facing and they both specify that that will be a problem if your estimatedRowHeight varies too much from your actual rowHeight. I was lucky that my cell's were close to the estimate but this is a known problem. – Numan Tariq Dec 12 '14 at 12:49
0

The problem is in your label's compression resistance: You have describe, which label is resist more to the height change: Try to set different values for the Content Hugging Priority -> Vertical and Content Compression Resistance Priority -> Vertical for the labels:

  • "Small List of Animals"
  • "List of animals"
  • "Even smaller list"

Also you use automatic UITableView cell's height calculation, but you need to setup table for it:

override func awakeFromNib() {
    super.awakeFromNib()
    tableView.estimatedRowHeight = 200; // You need approximately calculate this value by yourself, used mostly for the scroll indicator displaying
    tableView.rowHeight = UITableViewAutomaticDimension;
}

Or check your updated project https://dl.dropboxusercontent.com/u/48223929/LayoutingTest.zip

Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66
  • That still presents the issue when I tap Go and return. The cells are still all messed up. – Doug Smith Dec 08 '14 at 00:57
  • @DougSmith You right, my answer is not complete. I updated it – Vitalii Gozhenko Dec 09 '14 at 12:23
  • It works better, but it still has issues occasionally in your example project with tapping on cells (easily seen here, but visible too with the modal) and the offset changing when going back after. – Doug Smith Dec 12 '14 at 08:12
0

The solution is far easier than you may think. You have asked the storyboard to build a tableview with row height of 44. Select your tableView in Storyboard and check this:

Old tableView row height

You have to setup a tableView initial row height to something greater than what you may think your cell will expand, 200 for instance or even more:

Suggested tableView row height

Then, when iOS tries to build a new cell with the 200 height, it fills the label, calculates the sizes, and then it shrinks the cells.

Why does this happen?

I think -not sure- the clue is the way iOS builds tableViews. iOS always shows 8-10 cells on iPhone, if the cell initially is bigger than it's real calculated height, then iOS shrinks the cell and creates/dequeues a new cell to be shown -if needed-. If the cell needs to be bigger, then iOS may need to hide a row that initially was to be shown, and then it refuses to enlarge your cell. That's why you should always prefer building larger cells than smaller ones in height.

Pablo Romeu
  • 2,113
  • 1
  • 13
  • 15
  • I set it to 200, but when tapping on a cell, and then going back, the offset/position in the table view is still off. – Doug Smith Dec 12 '14 at 08:20
  • I tried this and it worked in your sample project. You mean the offset of the tableView or of some of the labels? – Pablo Romeu Dec 17 '14 at 10:22
0

The issue is not happening due to the modal view.

It's due to the row height you specified for the table view. Currently it's 44, change that to 200 or greater and everything will just work fine.

Storyboard

Also check the warnings in the Storyboard file.

Midhun MP
  • 103,496
  • 31
  • 153
  • 200
  • This seems like it helps, but when tapping on a cell then returning the offset/position in the table view is still pushed/not what it should be. – Doug Smith Dec 12 '14 at 08:20