I have a scenario in my app, in which I have to create a UITableViewCell content in runtime, depending on values I get from network. I'll try to explain what I try to achieve and the issues I am facing. Instead of a specific answer I would like to hear a general solution for this whole problem.
Attempts
I have a UITableView with a custom UITableViewCell which has some container views that could include views depending on runtime values.
UITableViewCell has been designed using storyboard with auto layout enabled.
For the views that would contain dynamic views, I have defined a height constraint to be v.height >= 0. But the height on interface builder is bigger than zero and I would expect cell re-layout after adding views.
The dynamic views are typically rows which are added vertically, but there is a scenario that I'm adding views horizontally.
I have used OAStackView, an alternative to UIStackView for pre-iOS9 devices.
I add my dynamic views on awakeFromNib method of my custom UITableViewCell
I tried to call some methods in hopes they would update my layout. The last one I used [self setNeedsUpdateConstraints]
On my view controller that includes UITableView I tried
myTableView.rowHeight = UITableViewAutomaticDimension;
myTableView.estimatedRowHeight = 300;
And
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Findings and Issues
My UITableViewCell seems to be created and the dynamic views. However it looks like the total height of the cell is exactly the same as the one I defined with storyboard.
View container heights are also the same as I designed them on storyboard, looks like v.height >= 0 is not the only constraint affecting those views (I haven't defined any other constraint regarding to height attribute)
The first view seem to be added nicely, but the other views are quite stretch (I think it's because no place for them)
Can you please help me to find a solution for this general problem? Thank you
Edit: cell image on request
I have a problem with the positioning of the elements inside a table cell. They change their position when I leaf through the table. I installed autoresizhe and indicated where they should be, but nalazeyut pictures to the text and I do not know how to fix them. I read a lot of articles but could not find a solution which is suitable for different devices. Please help me.
I have text and pictures change position on their own, when I leaf through or loaded a new cell in the table.
i add fixed size for uiimageview inside cellForRowAtIndexPath:
[cell.imageView setFrame:CGRectMake(0,0,self.view.frame.size.width,cell.imageView.frame.size.height)];
UPDATE:
i fixed my xib file. All constraints is blue. but the result is the same - bad. I do not know how to be. I suspect that the phone can not draw.
Since there is a warning and you discarded that warning for constraint the frame will be reset to that shown in yellow dotted rectangle. If any warning in the auto layout constraint, fix that first then only the IB will show correct frame that will be in run time.
If you need to set dynamic cell height based on the content you have, please refer this http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
I assume you set the table cell height properly before you jump into any autolayout stuff
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return x; //x is your table cell xib height
}
After you do this, LEt's go to your table cell. You provided the leading trailing top and bottom for all your elements which is good.
You dont even need to mention this in your cellForRowAtIndex method:
[cell.imageView setFrame:CGRectMake(0,0,self.view.frame.size.width,cell.imageView.frame.size.height)];
If you are trying to change the imageview frame after you set the constraints, you need to call
[cell.imageView updateConstraints];
[cell updateConstraints];
If you see any yellow warning in the autolayout, try to resolve it first. Xcode will suggest you some fixes for it, see that works for you ,by selecting element that has yellow warning and click on update frame option in storyboard.
See if it works!!!
In this iOS 8 app I'm creating, I have a tableview and I need them to be self resizing. I implemented it using Auto Layout and it works. Almost. Here's how it looks now.
There are 3 labels inside a cell. Main label which has the lorem ipsum text. Subtitle which has the string of numbers (Those are two separate labels. Might be confusing because they have the same color.) Then the third label with the small black text.
The first label resized itself correctly with no problem and the second label moves up and down accordingly. But the problem is with the third small label. As you can see, its not resizing itself to fit all the text.
Now there's a weird thing happening. I turn it landscape and here's it is.
Since there is space the label is displaying the entire text its supposed to. Fine. Then I turn it back to portrait.
Now the small label has resized itself to fit all its text but it overflows the cells boundaries. I tried making the cell bigger but it didn't work. Since this is self sizing cells, I don't think that's the correct way even.
I'm not getting any errors or even warning on my auto layout constraints either.
I have set these two lines of code in the viewDidLoad() method.
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
Can anyone please tell me what I might be doing wrong here?
Since its difficult to answer just by looking at images and I don't have any more code to post beside the above snippet, I uploaded a runnable Xcode project demonstrating the issue here. (There are 2 custom cells. Basically its the same cell just the height is increased in the second one.)
I've been fiddling with auto layout constraints but I can't seem to get this working. Any help would be appreciated.
Thank you.
UPDATE:
With the help of this tutorial I found some helpful pointers. According to it, each subview should have constraints that pin all its sides and there should be constraints that goes from top to bottom which helps auto layout to calculate the height of the cell. In my original post, I had vertical spaces between each label so I think that's the reason auto layout couldn't calculate the proper height.
So I made some changes.
I reduced the vertical space between labels to 0 and set the Vertical space constraints between top and middle labels and middle and bottom labels.
I added leading, top, trailing constraints to the top label.
Leading and trailing to the middle label.
Leading, bottom, trailing to the bottom label.
Now here's another weird part. When I first run it, the bottom label cropping issue is still there.
But if I rotate the device to landscape and turn it back to portrait, all the all the cells are resized properly to fit both labels!
Still can't figure out why this doesn't happen at first though. Updated Xcode project is here.
The issue here is with the multi-line labels' preferredMaxLayoutWidth property. This is the property that tells the label when it should word wrap. It must be set correctly in order for each label's intrinsicContentSize to have the correct height, which is ultimately what Auto Layout will be using to determine the cell's height.
Xcode 6 Interface Builder introduced a new option to have this property set to Automatic. Unfortunately, there are some serious bugs (as of Xcode 6.2/iOS 8.2) where this is not set correctly/automatically when loading a cell from a nib or Storyboard.
In order to work around this bug, we need to have the preferredMaxLayoutWidth set to be exactly equal to the final width of the label once it is displayed in the table view. Effectively, we want to do the following before returning the cell from tableView:cellForRowAtIndexPath::
cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)
The reason that just adding this code alone doesn't work is because when these 3 lines of code execute in tableView:cellForRowAtIndexPath:, 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, the label width is totally different from what it will end up being once the cell is displayed and its subviews have been laid out.
How do we get the label widths to be accurate at this point, so that they reflect their final width? Here's the code that makes it all come together:
// Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell
cell.bounds = 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.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)
OK, so what are we doing here? Well, you'll notice there are 3 new lines of code added. First, we need to set this table view cell's width so that it matches the actual width of the table view (this assumes the table view has already been laid out and has its final width, which should be the case). We're effectively just making the cell width correct early, since the table view is going to do this eventually.
You'll also notice that we're 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. The height of the cell or any of its subviews doesn't actually matter at this point, because we only care about getting the final widths for each label.
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. 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!
This is definitely an Apple bug in UIKit that we have to workaround for now (so please do file bug reports with Apple so they prioritize a fix!).
One final note: this workaround will run into trouble if your table view cell's contentView width doesn't extend the full width of the table view, for example when there is a section index showing on the right. In this case, you'll need to make sure that you manually take this into account when setting the width of the cell -- you may need to hardcode these values, something like:
let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth
cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999)
I met the same issue as you and I found a simple solution to resolve it.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// dequeue cell...
// do autolayout staffs...or if the autolayout rule has been set in xib, do nothing
[cell layoutIfNeeded];
return cell;
}
And the self-sizing worked well. In my code, I laid two labels in vertical, both of them are dynamic height. The height of cell is correctly set to contain the two labels.
Assuming you don't have any errors with your constraints as others have suggested, this problem seems to stem from using a UILabel that allows multiple lines in conjunction with a UITableViewCellAccessory. When iOS lays out the cell and determines the height, it does not account for the offset change in width that occurs because of this accessory, and you get truncation where you wouldn't expect to.
Assuming you want the UILabel to extend the full width of the content view, I wrote up a method that fixes this for all font sizes
-(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell {
float offset = 0;
switch ([cell accessoryType]) {
case UITableViewCellAccessoryCheckmark:
offset = 39.0;
break;
case UITableViewCellAccessoryDetailButton:
offset = 47.0;
break;
case UITableViewCellAccessoryDetailDisclosureButton:
offset = 67.0;
break;
case UITableViewCellAccessoryDisclosureIndicator:
offset = 33.0;
break;
case UITableViewCellAccessoryNone:
offset = 0;
break;
}
[label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8];
}
Simply put this in your cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
#Setup the cell
...
// Fix layout with accessory view
[self fixWidth:[cell label] forCell:cell];
return cell;
}
Do this for any labels that are going to have multiple lines to adjust the width properly and then recalculate the appropriate heights. This works with dynamic font sizes as well.
Like smileyborg had mentioned, if you weren't using the full width of the contentView you could reference the constraints and subtract them from the width as well.
Edit: I previously was running 'layoutIfNeeded' on the cell but this was creating performance issues and didn't seem to be needed anyway. Removing it hasn't caused any problems for me.
You have two problems here.
1/ Right now, using Visual Format Language, your cell's vertical constraints can be translated like this:
Cell: "V:|-(10)-[nameLabel]-(67)-|"
Then, you set a second group of constraints:
Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]"
Those two groups of constraints can't mix well and will reveal their ambiguity with your second problem.
2/ For some reasons, actionsLabel is limited to one line when you launch your app. Then, when you rotate your device to landscape mode, actionsLabel accepts to be displayed with two lines or more. Then, when you rotate your device back to portrait mode, actionsLabel keeps displaying two lines or more. But, because actionsLabel is not really part of your cell's height constraints, it overlap your cell's boundaries.
If you want to solve all those problems, I recommend first that you rebuild your xib file from scratch. This will cure your actionsLabel strange behavior (two lines or more only when you rotate your device).
Then, you will have to define your constraints like this:
Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|"
Of course, you can define other minimum height constraints for you labels than (>=21). In the same way, your bottom margin can be set to another value than -(10)-.
Addendum
In order to answer your question, I created a simple project with the previous constraints pattern in my .xib file. The following image may help you build your own constraints.
I tried the very easy and elegant looking solution of "fogisland" - and it did not work. Luckily I found out that one additional line makes it work in all directions. Just tell the system that you not only suggest a new layout (layoutIfNeeded), you explicitly ask for it (setNeedLayout)
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell
After updating to Xcode 6.1 beta 2 when I run my app that contains tableview cells, the debug assistant says:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Before, when I used Xcode 5 on this project, I would get a few errors but those have gone away since I upgraded. I have no other errors or warnings now. I have already tried adjusting the sizes of all the tableview cells and also tried using standard height but I still get the same warning:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
I have also read through all similar topics on this but none of their solutions help. When I test the app with the simulator, the app runs fine except the pictures that are supposed to be in the tableView cells aren't there.
You're encountering the side effect of a fantastic new feature in iOS8's Tableviews: Automatic Row Heights.
In iOS 7, you either had rows of a fixed size (set with tableView.rowHeight), or you'd write code to calculate the height of your cells and you'd return that in tableView:heightForRowAtIndexPath. Writing code for the calculation of a cell's height could be quite complex if you had numerous views in your cell and you had different heights to consider at different font sizes. Add in Dynamic Type and the process was a pain in the ass.
In iOS 8, you can still do the above, but now the height of the rows can be determined by iOS, provided that you've configured the content of your cell using Auto Layout. This is huge benefit for developers, because as the dynamic font size changes, or the user modifies the text size using Accessibility Settings, your UI can be adaptive to the new size. It also means if you have a UILabel that can have multiple rows of text, your cell can now grow to accommodate those when the cells needs to, and shrink when it does not, so there isn't any unnecessary whitespace.
The warning message you're seeing is telling you that there aren't enough constraints in your cell for Auto Layout to inform the tableview of the height of the cell.
To use dynamic cell height, which, along with the techniques already mentioned by other posters, will also get rid of this message, you need to ensure your cell has sufficient constraints to bind the UI items to the top and bottom of the cell. If you've used Auto Layout before, you are probably accustomed to setting Top + Leading constraints, but dynamic row height also requires bottom constraints.
The layout pass works like this, which occurs immediately before a cell is displayed on screen, in a just-in-time manner:
Dimensions for content with intrinsic sizes is calculated. This includes UILabels and UIImageViews, where their dimensions are based on the text or UIImages they contain, respectively. Both of these views will consider their width to be a known (because you've set constraints for trailing/leading edges, or you set explicit widths, or you used horizontal constraints that eventually reveal a width from side to side). Let's say a label has a paragraph of text ("number of lines" is set to 0 so it'll auto-wrap), it can only be 310 points across, so it's determined to be 120pt high at the current font size.
The UI is laid out according to your positioning constraints. There is a constraint at the bottom of the label that connects to the bottom margin of the cell. Since the label has grown to be 120 points tall, and since it's bound to the bottom of the cell by the constraint, it must push the cell "down" (increasing the height of the cell) to satisfy the constraint that says "bottom of the label is always standard distance from the bottom of the cell.
The error message you reported occurs if that bottom constraint is missing, in which case there is nothing to "push" the bottom of the cell away from the top of the cell, which is the ambiguity that's reported: with nothing to push the bottom from the top, the cell collapses. But Auto Layout detects that, too, and falls back to using the standard row height.
For what it's worth, and mostly to have a rounded answer, if you do implement iOS 8's Auto Layout-based dynamic row heights, you should implement tableView:estimatedHeightForRowAtIndexPath:. That estimate method can use rough values for your cells, and it'll be called when the table view is initially loaded. It helps UIKit draw things like the scrollbar, which can't be drawn unless the tableview knows how much content it can scroll through, but does't need totally accurate sizes, since it's just a scrollbar. This lets the calculation of the actual row height be deferred until the moment the cell is needed, which is less computationally intensive and lets your UITableView be presented quicker.
Three things have managed to silence this warning so far. You can pick up the most convenient for you. Nothing pretty though.
To set up default cell's height in viewDidLoad
self.tableView.rowHeight = 44;
Go to storyboard and change row height on your tableview to something different than 44.
To implement tableview's delegate method heightForRowAtIndexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
Weird.
To resolve this without a programmatic method, adjust the row height of the table view in the Size Inspector from the storyboard.
I had this problem after creating a custom UITableViewCell and adding my subviews to the cell instead of its contentView.
This is an autolayout issue. Make sure that your subviews have all the constraints. For me, the bottom constraint was missing for the Title Label in the cell. When I added that, the warning went away and everything showed up perfectly.
Just enable Self-Sizing Table View Cells
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
& make sure you added constraints on all sides of UITableViewCell as-
Example Link 1
Example Link 2
If u are using static cell or dynamic cell ,simply add some row height to table view in inspector table and uncheck the automatic to the right side of row height ,that's it u will stop getting this warning .
I got this warning today. Here is what made it disappear for me(in interface builder)
1.Set the row height field for the table view to something other than 44
2 Set the row height field for the tableView cell to something other than 44
I did not have to make any changes in code
In my case, I was building the cell programmatically and kept getting this error.
I was adding the subviews and constraints in the UITableViewCell's init method like this:
addSubview(rankingLabel)
addConstraints(cellConstraints)
I solved the issue by adding them to the cell's contentView instead:
contentView.addSubview(rankingLabel)
contentView.addConstraints(cellConstraints)
Set the estimated row height to zero and the warning disappears:
If you have created a Custom tableViewCell for tableView, make sure you have given both bottom and top constraints to you cells,
you could also get this message if your subviews inside custom cells are aligned in center Y which wouldnt pop any error message but would mess up with identifying height of row for tableview in turn like in Image I have attached , here we have both top and bottom constraints
When you create a Custom Cell for tableView you must specific row height or top and bottom constraints for you custom cell's subviews inside cell (e.g. label in custom cell like in below image)
But if this doesn't work you can try setting row height for your cell instead of being automatic like in this image
But be sure if you turn that automatic tick off you have to adjust your row size for changes programmatically which could have been done automatically
I got this Warning today All I did is just added one extra line to my code
tableView.rowHeight = 200;
add this line of code inside the
func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
...
}
and the final code look like
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tableView.rowHeight = 200;
...
}
this code will increase the table Row cell height to 200 the default height is 44
I too experienced this warning with moving to Xcode 6 GM. I was only getting the warning when I rotated the device back to its original position.
I am using custom UITableViewCells. The storyboard table view is set to my custom size (100.0 in my case). While the table cells render properly as they have in previous releases, I did not like warning message.
In addition to the above ideas, I added this
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100.0;
}
Screen renders... responds to rotation and no more warning messages.
In xcode 6.0.1 I had removed this warnings specifying the row height using:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44.0;
}
You may also see this message if your only constraints are set to align all items vertically and you don't have/want a height specified for the cell. If you set a top/bottom constraint on the item the warning will disappear.
I had this problem when my labels and views in the custom tableViewCell were constrained to the customCell, not its Content View. When I cleared the constraints and connected them to cells Content View the problem was solved.
I had the same error message,
make sure all your outlets are valid like table view and tableview Constraints
I have also similar issue for custom tableview cell which has dynamic row height. Dynamic height wasn't reflected and got the same warning in console. The solution is Adding subviews to cell instead of contentView. BTW, I have created subviews programatically.
I have this issue on TableViewCells where the constraints are set on initialisation but where the cell's contents are loaded afterwards, this means the autolayout engine can't determine the height. The other solutions here don't work because I need the cell's height to be UITableView.automaticDimension.
I just added an extra constraint to the cell:
contentView.heightAnchor.constraint(equalToConstant: 44, priority: .defaultLow)
In the storyboard set the cell Row height field with the same value as Row height in tableView (both with the same value worked for me).
If you add heightForRowAtIndexPath function to your code it may induce a performance issue because it will be called for each cell so be careful.
If you are making a dynamic height calculation,
you should have all elements linked to each other in terms of constraints like top and bottom.
you should definitely have a bottom constraint that is linked to the element at the bottom of your cell
if you are extending your ViewController class with UITableView and also using navigation controller to show the screen then you dont need to perform segue with identifier this may cause an error of identifier ViewController, you can use pushViewController method to show the chat screen in order to get rid from this error so here is the code just paste it in to your UItableView delegate
let chatBox = ChatBoxViewController()
navigationController?.pushViewController(chatBox, animated: true)
just put the name of your viewcontroller which you want to show next and yeah done.
I have same error, due to this line this error was shown.
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2) as! CGColor
I just change the line as following to fix the error
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2).cgColor
I have a strange issue with the height of the table view cells. I'm creating a custom grouped style UITableView in the storyboard. I have 4 different prototype cells for a single, top, middle and a bottom cell. Each of them has a different height. I'm already setting those heights in the storyboard, and I've also implemented the delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Just for an example: The single cell should have a height of 48. So I set the height in the storyboard to 48 and the above mentioned delegate method returns the value 48. But if I take a look at the screenshot, the single cell has actually a height of 49.
There is the same behavior for the top cell, but not for the middle and the bottom cell. That makes it even stranger.
Do you have any ideas? Where comes this extra pixel from?
I don't know exactly but it may be reason check the height of the header and footer .