AutoLayout in UITableview with dynamic cell height - ios

For dynamic height of my table view cell I take reference from this link.
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Here is my code of tableview data source and delegate methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return arrTemp. count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"AutoLAyoutCell";
AutoLayoutTableViewCell *cell=(AutoLayoutTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell==nil) {
for (id currentObject in [[NSBundle mainBundle] loadNibNamed:#"AutoLayoutTableViewCell" owner:self options:nil]) {
if ([currentObject isKindOfClass:[UITableViewCell class]]) {
cell = (AutoLayoutTableViewCell *)currentObject;
break;
}
}
}
cell.IBlblLineNo.text=[NSString stringWithFormat:#"Line:%i",indexPath.row];
cell.IBlblLineText.text=[arrTemp objectAtIndex:indexPath.row];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGSize expectedlineLabelSize = [cell.IBlblLineText.text sizeWithFont:cell.IBlblLineText.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByTruncatingTail];
cell.IBlblLineText.numberOfLines=expectedlineLabelSize.height/17;
CGRect frmlbl=cell.IBlblLineText.frame;
frmlbl.size.height=expectedlineLabelSize.height;
cell.IBlblLineText.frame=frmlbl;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
cell.IBlblLineNo.text=[NSString stringWithFormat:#"Line:%i",indexPath.row];
cell.IBlblLineText.text=[arrTemp objectAtIndex:indexPath.row];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGSize expectedlineLabelSize = [cell.lineLabel.text sizeWithFont:cell.lineLabel.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByWordWrapping];
cell.IBlblLineText.numberOfLines=expectedlineLabelSize.height/17;
CGRect frmlbl=cell.IBlblLineText.frame;
frmlbl.size.height=expectedlineLabelSize.height;
cell.IBlblLineText.frame=frmlbl;
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1.0f;
return height;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
CGSize expectedlineLabelSize = [cell.IBlblLineText.text sizeWithFont:cell.IBlblLineText.font constrainedToSize:CGSizeMake(280, 1000) lineBreakMode:NSLineBreakByTruncatingTail];
return expectedlineLabelSize.height;
}
I have 2 questions :
My issue is I get the error EXE_BAD_EXCESS near the line
AutoLayoutTableViewCell *cell = (AutoLayoutTableViewCell *)[IBtblAutoLayoutExample cellForRowAtIndexPath:indexPath];
in heightForRowAtIndexPath and estimatedHeightForRowAtIndexPath.
Why do I have to write label text in both cellForRowAtIndexPath and heightForRowAtIndexPath?
Also, am I missing anything needed to achieve dynamic height for the cell?

To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.

Related

UITableView self sizing cell scrolling issues with big difference of cell height

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let json = JSON(data: activityTableView.onlineFeedsData)[indexPath.row] // new
if(json["topic"]["reply_count"].int > 0){
if let cell: FeedQuestionAnswerTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier_reply) as? FeedQuestionAnswerTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedWithReplyCell(cell, indexPath: indexPath, rect: view.frame,key: "activityTableView")
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}else{
if let cell: FeedQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as? FeedQuestionTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedCell(cell, indexPath: indexPath, rect: view.frame)
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}
return UITableViewCell()
}
There is difference of 200-400 in height in each cell,So tried implementing the height calculation in estimatedHeightForRowAtIndexPath .I cant the exact estimate height in this method as bcz of calculation
and caching the height in willDisplayCell method but still the scroll jumps like is it taking time to rendering .
The cell is like the Facebook card type layout with lots of dynamic data with variable height text and images.Can someone help me in this .Thanks
If you're able to calculate each cells height, just use the tableView(_:heightForRowAtIndexPath) instead of estimatedRowHeightAtIndexPath property. estimatedRowHeightAtIndexPath is mostly used in smthg like selfsizing cells etc.
Try this one. I hope this could help you. It's worked for me. Actually this calculates the cell height before it displayed.
Thanks.
NSMutableDictionary *cellHeightsDictionary;
// Put this in viewDidLoad
cellHeightsDictionary = #{}.mutableCopy;
// UITableview delegate Methods
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
NS_AVAILABLE_IOS(7_0)
{
NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
if (height) return height.doubleValue;
return UITableViewAutomaticDimension;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cellHeightsDictionary setObject:#(cell.frame.size.height) forKey:indexPath];
}

Size Class Customization in UITableViewCell

I have a height constraint in UIImageView which is contained in UITableViewCell,
and I want it to be 180 for iPhone and 300 for iPad.
But it doesn't make any effect for iPad.
It is a table view with automatic dimension.
- (void)configureTableView {
self.tableView.allowsSelection = NO;
self.tableView.estimatedRowHeight = 30.f;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
How can I customize cell's height for iPad?
update:
I fixed it by implementing delegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = -1;
if (indexPath.section == kQuizControllerSection_Description && indexPath.row == kDescriptionQuizCell_PreviewImage) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
height = 400;
}
else {
height = 180;
}
}
return height;
}
because other cells are resized dynamically (cells with multiline labels) method returns negative number for every other cell. Is it correct approach?
I there is only UIImageView with the same height as cell ....then just pinned all edges of UIImageView and then there is not any need of height constraint of UIImageView...
You just need to set height of cell according to device in heightForRowAtIndexPath method
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if UIDevice.currentDevice().userInterfaceIdiom == .Pad{
return 300
}
else{
return 180
}
}
The other scenario where UIImageView is not the same height as cell....then make a one IBOutlet of height constraint of UIImageView and then change the constant as per your requirement...
if UIDevice.currentDevice().userInterfaceIdiom == .Pad{
heightconstraint.constant = 300
}
else{
heightconstraint.constant = 180
}
This will do it:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// default height is 180
int height = 180;
// if your device is an iPad then it's 300
if (UIDevice.currentDevice().model.rangeOfString("iPad") != nil)
height = 300;
// if you want the UIImageView to follow the height, just go with this:
yourImageView.frame = CGRectMake(0, 0, tableView.frame.size.width, height);
return height;
}

Custom Table View Cell with a big image and a label

I am trying to populate a Tableview with data from an array. I am trying to make a custom table view cell that includes a big square image, same width as the screen and the height should also be the same size as the screen's width. And under the image I want to add a text label. (You can think of it as a very bad copy of instagram)
However, the tableview only shows empty standard cells.
This is my custom cell code:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
customImageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame;
frame.origin.x=0;
frame.origin.y=0;
frame.size.width=self.frame.size.width;
frame.size.height=self.frame.size.width;
customImageView.frame=frame;
[customTextLabel sizeToFit];
frame.origin = CGPointMake(0, (self.frame.size.width+1));
customTextLabel.frame = frame;
customTextLabel.numberOfLines = 0;
customTextLabel.lineBreakMode = NSLineBreakByCharWrapping;
[self.contentView addSubview:customImageView];
[self.contentView addSubview:customTextLabel];
[self.contentView sizeToFit]; } return self;}
and the code from the tableview:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyCellIdentifier = #"MyCellIdentifier";
//UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
FeedTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
if(cell == nil) {
cell = [[FeedTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyCellIdentifier];
}
if(!emptyTable){
Log *log = [self.feedPosts objectAtIndex:indexPath.section];
cell.customImageView.image = [self loadImage:(int)log.logID];
cell.customTextLabel.text = log.logDescription;
}
return cell;}
Better solution is to use constraints
create constraints like on image below
Create outlets for your height width ( control drag your constraint to cell class)
Change it to screen size/ height on your
cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
cell.heightConstraint.constant = UIScreen .mainScreen().bounds.height;
cell.widthConstraint.constant = UIScreen.mainScreen().bounds.width;
return cell
}
Don't forget to set height for row
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UIScreen .mainScreen().bounds.height;
}
Also, you can recreate it on your code programmatically, if you want
Hope this helps
Where are you allocate memory for the cell.customImageView and cell.customTextLabel? This problem should be caused by not generating customeImageView and customeTextLabel. you should according to the following code to modify:
//Setter/Getter
//allocate memory and init customImageView/customTextLable
- (UIImageView *)customeImageView{
if(!_ customImageView)_customImageView = [UIImageView new];
return _customImageView;
}
- (UILable *)customTextLabel{
if(!_customTextLable) _customTextLabel = [UIlable new];
return _cusomTextLable;
}
Then invoke them when initWithStyle:reuseIdentifier:.The other thing should be noticed is that tableView should custome cell's height by implement tableView:heightForRowAtIndexPath:
I almost did what DarkHorse said.
I forgot to do:
customImageView = [[UIImageView alloc] init];
customTextLabel = [[UILabel alloc] init];
and then set the height in
tableView:heightForRowAtIndexPath

UITableViewCell constraints not seen in iOS 8. How do I mitigate this?

I'm trying to understand why the constraints I have put in place on the Storyboard for my different labels in my cell aren't being done. This is important because my height is dynamic.
I have had this problem for the past 2 days and it's driving me up the wall. No, UITableViewAutomaticDimension is not how I want to do this.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = self.feed.dequeueReusableCellWithIdentifier("post") as PostCell
var post = self.posts[indexPath.row]
cell.name.text = post.name
cell.timestamp.text = post.timestamp
cell.postBody.text = post.body
println("\(cell.name.constraints())")
println("\(cell.postBody.constraints())")
cell.contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(feed.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
var height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height
height += 1.0
return height
}
When I print the constraints set for cell.postBody I get []. However, I have 5 constraints. A trailing space to Superview, leading space to Superview, Bottom space to Superview Equals 4, and 2 Top Spaces to 2 different labels Equals 8.
If it isn't possible for my code to see the constraints via the Storyboard, how do I programmatically set these 5 constraints
Updated way I'm doing it:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = self.feed.dequeueReusableCellWithIdentifier("post") as PostCell
var post = self.posts[indexPath.row]
cell.name.text = post.name
cell.timestamp.text = post.timestamp
cell.postBody.text = post.body
if cachedHeights[post.id] == nil && cell.bounds.height != 0.0 {
cachedHeights[post.id] = cell.bounds.height
}
return cell
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var post = posts[indexPath.row]
if cachedHeights[post.id] != nil {
return cachedHeights[post.id]!
} else {
return 70
}
}
The problem is, I'm not sure cell.bounds.height is completely accurate. In that I mean, I think it is using the height of a previous large cell sometimes (perhaps from the cell it dequeued for the new one.)
The constraints aren't on the labels. That's why their constraints property returns an empty array.
You'll find the constraints on the labels' superview, cell.contentsView.
You can skip the calls to setNeedsUpdateConstraints and updateConstraintsIfNeeded as layoutSubviews will call updateConstraintsIfNeeded.
The old approach I used before switching to self-sizing cells:
I don't have any swift code, but here's some old code from an app before I switched over to self-sizing cells. It (implicitly) uses the constraints placed on the labels in the storyboard cell. The only thing different I did is caching the sizing cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static LeftDetailTableViewCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
});
// configure the cell for this indexPath
[self configureCell:cell atIndexPath:indexPath];
// Set the sizing cell's width to the tableview's width
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
// get the fitting size
CGSize s = [cell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
return s.height + 1.0;
}
Update:
Here's the code I'm using now, for self-sized cells in iOS 8.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BIBLETableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[cell adjustSizeToMatchWidth:CGRectGetWidth(self.tableView.frame)];
[self configureAccessoryTypeForCell:cell forRowAtIndexPath:indexPath];
[cell adjustConstraintsToMatchSeparatorInset:self.tableView.separatorInset];
[self configureCell:cell forRowAtIndexPath:indexPath];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
return cell;
}
My subclassed cell has:
- (void)adjustSizeToMatchWidth:(CGFloat)width
{
// Workaround for visible cells not laid out properly since their layout was
// based on a different (initial) width from the tableView.
CGRect rect = self.frame;
rect.size.width = width;
self.frame = rect;
// Workaround for initial cell height less than auto layout required height.
rect = self.contentView.bounds;
rect.size.height = 99999.0;
rect.size.width = 99999.0;
self.contentView.bounds = rect;
}
- (void)adjustConstraintsToMatchSeparatorInset:(UIEdgeInsets)inset
{
if (self.leadingMargins)
{
for (NSLayoutConstraint *constraint in self.leadingMargins)
{
constraint.constant = inset.left;
}
}
if (self.trailingMargins)
{
for (NSLayoutConstraint *constraint in self.trailingMargins)
{
constraint.constant = self.accessoryType == UITableViewCellAccessoryDisclosureIndicator ? 0.0f : inset.left;
}
}
}

Current cell height inside the heightForRowAtIndexPath?

I have a table with static cells. For one cell I want to change its height depending on the label's height (inside that cell) and at the same time leave all other cells height intact. How can I get current cell's height? Or maybe there is better approach?
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath section] == 2) {
return self.myLabel.frame.origin.y *2 + self.myLabel.frame.size.height;
} else {
return ...; // what should go here, so the cell doesn't change its height?
}
}
You can call:
[super tableView:tableView heightForRowAtIndexPath:indexPath]
in the else block so you don't have to worry if ever you changed the default height.
You can get/set default height tableView.rowHeight, or you can store your height before changing cell height, so you can get default height from some variable;
Please do this one
if ([indexPath section] == 2)
{
if(indexPath.row == 1)
return self.myLabel.frame.origin.y *2 + self.myLabel.frame.size.height;
else
tableView.rowHeight
}
#talnicolas great answer, here's for Swift 3:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 2 {
let easy = self.myLabel.frame
return easy.origin.y *2 + easy.size.height
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
You, maybe, want to calculate height of label in constrained width. In this case you can create method like that:
- (CGFloat)textHeightOfMyLabelText {
CGFloat textHeight = [self.myLabel.text sizeWithFont:self.myLabel.font constrainedToSize:LABEL_MAX_SIZE lineBreakMode:self.myLabel.lineBreakMode].height;
return textHeight;
}
and use result in - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.
Don't forget to add margin value of you label.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == youSectionNumber)
{
if (indexPath.row == numberOfrow)
{
return self.myLabel.frame.origin.y *2 + self.myLabel.frame.size.height;
}
}
return 44; // it is default height of cell;
}

Resources