Unexpected behavior of systemLayoutSizeFittingSize: and size classes - ios

I've got some strange problem related to dynamically sized cells, auto layout and size classes. My test project is completely based on Ray's tutorial. It seems that the only difference is UILabels's font sizes for size classes.
The height calculation is wrong when I set another font size for label in some size class. I've made screenshot to illustrate it.
Wwith wrong cell's height calculations:
With correct cell's height calculations:
In addition, I've pushed test project to the github.
EDITED:
ViewController.m
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForCellAtIndexPath:indexPath];
}
- (CGFloat)heightForCellAtIndexPath:(NSIndexPath *)indexPath {
static CustomTableViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:kBasicCell];
});
[self configurateCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForCell:sizingCell];
}
- (CGFloat)calculateHeightForCell:(CustomTableViewCell *)cell {
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.frame), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height +1.0f;
}
CustomLabel.m
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[self setNeedsUpdateConstraints];
}

Since you're already using Auto Layout, I'd recommend also taking advantage of self-sizing cells. You won't need any row height calculation, as iOS can adjust the cell's height automatically, based on the UILabel height.
Add Auto Layout constraints to your contentView's UILabel. This will cause the cell to adjust its contentView height based on the label's contents.
Enable row height estimation.
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.

Related

Set Dynamic Cell's Table View Content height to table view height constraints ios

I have tableview(A)'s every custom cell having tableview(B) with dynamic table view cell.
At tableview(A) cellForRowAtIndex.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
MainMessageTVCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MsgMainCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSInteger index = indexPath.row;
MessageMain *result = tableData[index];
cell.dateLabelTC.text = [NSString stringWithFormat:#"Date : %#",result.createdTime];
cell.subjectLabelTC.text = [NSString stringWithFormat:#"Subject : %#",result.subject];
NSArray *arrList = result.messageList;
[cell setupTableData:(NSMutableArray *)arrList];
[cell setNeedsUpdateConstraints];
}
At tableview(A)'s custom cell reload tableview(B).
-(void)setupTableData:(NSMutableArray *)tableData{
_tableData = tableData;
[self.tableView reloadData];
}
-(void)updateConstraints{
[super updateConstraints];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView layoutIfNeeded];
CGFloat height = self.tableView.contentSize.height;//+1000;
tableBHeightConstraints.constant = height;
});
}
tableBHeightConstraints is height constraints of table view(B) in tableview(A)'s cell's child.
tableBHeightConstraints.constant not getting correct value with all calculate constraints.
what is the best place or method to get tableView.contentSize.height exact after dynamic table cell's height set.
This is tableview(A)'s Cell
This is tableview(B)'s Cell
Please Help guys.
Add the following code in you viewDidLoad method.
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView setEstimatedRowHeight:85.0];
Also include the estimatedHeightForRowAtIndexPath method and return a estimated row height as follows,
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
Assign an automatic dimension for heightForRowAtIndexPath.The method asks the delegate for the height to use for a row in a specified location.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
NOTE
For your table row to be of dynamic height, the labels within the contentview has to be pinned to the top and bottom so that they can shrink or grow as per the contents. Do update me in case if you are facing any difficulty.
Try This :
take a height constraint of UItableView
_tableViewHieghtConstraint.constant = height of row *count of array.
in tableview number of section method.
If you can't get this to work, then why not try something different. Instead of trying to stick a tableView in a tableCell, why not just modify your datasource to govern the two tables as one. That is probably a better solution as is should be more efficient and work better as a single table.
If you use grouped style, then effectively each group is an individual tableView with a header and footer.
It's better to simply set the constraint to equal height:
tableA_cell.contentView height = TableB height
With visual constraints it would be something like this:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(tableB);
[cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V|[tableB|" options:0 metrics:0 views:viewsDictionary];
Then the magic should happen by itself.
If you make any changes to tableB, just call
[self.tableView beginUpdates]
[self.tableView endUpdates]
on table A to automatically update the height of it's cells
Try moving your changes from -(void) updateConstraints into -(void) viewDidLayoutSubviews.
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView layoutIfNeeded];
CGFloat height = self.tableView.contentSize.height;//+1000;
tableBHeightConstraints.constant = height;
});
}

Force UITableView to load cells before it is displayed

I need to force UITableView to load all cells based on datasource and get resulting UITableView height before it will be displayed on UI. I have other logic to be implemented based on that height. My rows have dynamic height and content inside cells is autolayouted. That is why I coudln't calculate it manually (or don't know how). It is crucial in my case to get the height before UITableView is displayed.
Please advise.
Now, the requirement is to find the height of cells with filled data
By this, you can find height of cells before displaying them on tableView
Yes, you can do that by using below code:
/****/
- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier= #"IDENTIFIER";
static dispatch_once_t onceToken;
static UITableViewCell *cell = nil;
dispatch_once(&onceToken, ^{
cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
});
/**SET ALL DATA AND VALUES ON CELL*/
CGFloat height = [self calculateHeightForConfiguredSizingCell:cell];
// NSLog(#"height at indexPath %ld --> %f",(long)indexPath.row, height);
return height;
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGFloat height = size.height;
return height + 1.0f; // Add 1.0f for the cell separator height
}

Custom UITableViewCell constraint issue on cell reuse

I have a custom UITableViewCell that has a dynamic image height. All the resizing works as its supposed to when the cell is created. The issue is once the cell is reused by the UITableView the constraint for the image height is changed and causes an “Unable to simultaneously satisfy constraints” error.
I have tried deactivating the height constraint before resetting it but it still causes the error.
With regards to setting the height anchor. This creates a new instance each time, are the deactivated constraints released from memory? Is there a way of just updating the current constraint in this format?
Should i be doing something within - (void)prepareForReuse method?
Thanks for any help
// Within Custom UITableViewCell
// The cell is a prototype cell in StoryBoard
// The width of the image is fixed and already set
// Observe when the image is set for imgView
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:#"image"]) {
if(self.imgView.image) {
CGFloat ratio = self.imgView.image.size.height / self.imgView.image.size.width;
self.imageHeightConstraint.active = NO;
self.imageHeightConstraint = [self.imgView.heightAnchor constraintEqualToAnchor:self.imgView.widthAnchor multiplier:ratio];
self.imageHeightConstraint.identifier = #"imageHeightConstraint";
self.imageHeightConstraint.active = YES;
}
}
}
The UITableViewCell auto resizes as the UIImageView is in a UIStackView which has its top and bottom anchors fixed to the top and bottom of the cells contentView.
- (void)buildUI {
UIStackView *containerStackView = [UIStackView new];
[self.contentView addSubview:containerStackView];
containerStackView.translatesAutoresizingMaskIntoConstraints = NO;
containerStackView.axis = UILayoutConstraintAxisHorizontal;
containerStackView.distribution = UIStackViewDistributionFill;
containerStackView.alignment = UIStackViewAlignmentCenter;
containerStackView.spacing = 10;
[containerStackView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:10.0].active = YES;
[containerStackView.centerXAnchor constraintEqualToAnchor:self.contentView.centerXAnchor].active = YES;
[containerStackView.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor multiplier:kImageWidthMultiplier].active = YES;
[containerStackView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-30.0].active = YES;
self.imgView = [UIImageView new];
[containerStackView addArrangedSubview:self.imgView];
self.imgView.translatesAutoresizingMaskIntoConstraints = NO;
[self.imgView.widthAnchor constraintEqualToAnchor:containerStackView.widthAnchor multiplier:0.492].active = YES;
}
It is quite complicated for calculate dynamic table view cell height. In you case, cell height depend on image height.
When table view cell is reused, all cell's properties will be reused and the height is not exception. The easy way to make change is using heightForRowAtIndexPath, but it very hard when using autolayout to calculate height.
According this article (http://www.raywenderlich.com/87975). The subview in table view cell will be compressed and after that, we could easy to get height and return in heightForRowAtIndexPath. However, it take a little time to calculate and make your scroll is not smooth, but we could using estimatedRowHeight, your tableview will use the estimate height and apply for specific row before we can calculated exactly height for this row
The below is short demo code:
//Estimate height (height could be 35, 42, 50, 29, etc)
//Just estimate a number
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
return 40.0f;
}
//Your exactly height for row
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[yourCustomCell setNeedsLayout];
[yourCustomCell layoutIfNeeded];
CGSize size = [yourCustomCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//only set once
static YourCustomTableViewCell *yourCustomCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//load custom cell
yourCustomCell = (YourCustomTableViewCell*)[Utils getCellNibFile:#"YourCustomTableViewCell" Owner:self];
});
//get image from datasource
UIImage* image = [self.dataSource objectAtIndex:indexPath];
//set image for cell and begin calculate height after set image
[yourCustomCell setImage:image];
[self calculateHeightForConfiguredSizingCell:yourCustomCell];
return height;
}

Wrong UITableViewCell height with UITableViewAutomaticDimension

I make custom cell with 2 multiline labels and pin this labels to all sides. When in -tableView:heightForRowAtIndexPath: for iOS >= 8 I return UITableViewAutomaticDimension. But when table view appears the cell height is bigger than should be. After I scroll down and then scroll up the cell takes the normal height. How to fix this behavior?
Code for height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
return UITableViewAutomaticDimension;
}
static CCTopicTableViewCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:#"topicCellIdentifier"];
});
cell.topic = [self.topics topicAtIndexPath:indexPath];
cell.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1;
}
And the result:
After scrolling:
The problem is gone when I don't set tableView's estimatedRowHeight for iOS 8 and update preferredMaxLayoutWidth every time when sets the text for multiline labels
I solved this issue by setting "text" property of multiline labels to "nil":
override func prepareForReuse() {
_label.text = nil
}
Also table properies "estimatedRowHeight" and "rowHeight" should be set correctly:
_tableView.estimatedRowHeight = 44
_tableView.rowHeight = UITableViewAutomaticDimension
Automatic dimension is not working well for some UILabel if that label is aligned with different label by top edges or bottom edges. Just make sure that you do not have such alignments.
Set automatic dimension in viewDidLoad() method or viewWillAppear() (both code in same place)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 160.0;//(Maximum Height that should be assigned to your cell)
Visit Using Auto Layout in UITableView for dynamic cell layouts & variable row heights an intuitive guide for both iOS 7 and iOS 8

Resize tableview NSLayoutConstraint

Hi I have a tableView which I change the width programmatically by his constraint:
self.widthTableLeft.constant = self.view.frame.size.width;
I do this in the viewDidLoad.
In the delegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
I need the contentview frame of each cell to calculate the hight of the cell, but when get it, this is the old size, I mean the size if I wouldn't have done the resize.
When the table is showed the size of the table is right but the hight of the cell is wrong.
I have tried to call:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
The question is, before to show the view of my view controller, when is the best place to update the width constraint of a UITableView and to get the right contentView in the cells in the method heightForRowAtIndexPath.
Sorry but I'm lost, could you give me any ideas?
Thanksssss
UPDATE
This is the code in heightForRowAtIndexPath
static const NSInteger ROC_TITLE_LABEL_TAG = 2;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
ROCAgendaItemDetailEntity *agendaItemDetail = [self.listRight objectAtIndex:indexPath.row];
UITableViewCell *cell = [self.tableLeft dequeueReusableCellWithIdentifier:#"cellVideo"];
//I get the label which will make the cell bigger
UILabel *titleLabel = (UILabel *)[cell viewWithTag:ROC_TITLE_LABEL_TAG];
//We init the height title with the min height.
float extraSpaceHeightLabel = 0;
if (agendaItemDetail.agendaItem.title.length > 0) {
//I will use his width to get the hight that I need to write all text
float width = titleLabel.frame.size.width;
CGSize size = CGSizeMake(width, CGFLOAT_MAX);
//we get the size
CGSize sizeTitleLabel = [agendaItemDetail.agendaItem.title sizeWithFont:[ROCTextsInformation imagoMed:15] constrainedToSize:size lineBreakMode:titleLabel.lineBreakMode];
//we substract the current height of the label to know the extra space to write all text
sizeTitleLabel.height -= titleLabel.frame.size.height;
extraSpaceHeightLabel = sizeTitleLabel.height;
}
//The final hight will be the contenViewHeight pluss the extra space needed.
return cell.contentView.frame.size.height + extraSpaceHeightLabel;
}
and this is the cell in the table view
Thanks again
viewDidLoad is too early to try and access the geometries of your views. Instead, access self.view's width in viewDidLayoutSubviews.
#interface CustomViewController ()
#property (nonatomic) BOOL isFirstTimeViewDidLayoutSubviews; // variable name could be re-factored
#end
#implementation CustomViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.isFirstTimeViewDidLayoutSubviews = YES;
}
- (void)viewDidLayoutSubviews
{
// only after layoutSubviews executes for subviews, do constraints and frames agree (WWDC 2012 video "Best Practices for Mastering Auto Layout")
if (self.isFirstTimeViewDidLayoutSubviews) {
// execute geometry-related code...
self.widthTableLeft.constant = self.view.frame.size.width;
}
self.isFirstTimeViewDidLayoutSubviews = NO;
}

Resources