Related
I have a dynamic list of items, each item could have different teamplate/layout (and height). And one those item types could have an internal list of items to select from, regularly 5-6 rows, each has different height.
If I try to describe it further, in my scenario I have one tableview (#slave) inside tableviewcells (#master-cell) of another tableview (#master). Moreover cells (#slave-cell) in my #slave tableview could have different height as well. So I need to layout my #slave to have #master automatically calc and update its size.
I have the issue with the inner table (#slave). In case of auto-layout, to fit all the cell space, the table will be collapsed unlike UILabel or other controls. So what I need here is to get the projected size of #slave table and set the height of the #slave = content height of the #slave.
I found similar post and it works if all rows have the same height but I'm using custom rows with dynamic height so the tableView.contentSize.Height gives me invalid result (basically just multiply rowNumbers * estimatedRowHeight, you can see it on the screenshot, master item #3 has 4 inner cells). Even after calling #slave.reloadData I couldn't get that size.
What is the proper way to build that kind of UI?
Source code with a test project attached (Xamarin.iOS)
I just ran into the same problem a few days ago,and tried to work it around.
The #master-cell works like a childViewController,it's the delegate datasource of the #slave TableViewController.But you cann't have a childViewController in the UITableViewcell.
Customize UITableViewCell to hold necessary property and acts as #slave TableViewController's delegate datasource,and configure #slave-cell's
height and data.
The real problem is the height for #master-cell,
If your data is simple and static,you can compute the height in advance,and return it in method func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat of the ViewController.
Otherwise,add a method to #master-cell which return the height for the whole cell when its property is set.And create a proxy #master-cell to compute the height and return it :
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = CustomUITableViewCell();
let model = self.getModel(indexPath)
cell.model = model
let height = cell.requiredHeight()
return height;
}
It's complex and expensive,but it works.
I think you do not have need of take UITableView inside UITableView. You can take more than one section in UITableView. And use different cellReuseIdentifier. This way your goal will be achieved.
For such a layout ios provide section in tableview, for master items use SectionView(there is delegate method for sectionView -> in which you can provide view for a section) and as different section may have different type of row so make rows according your need and return them according to section.
Perhaps it is because I do not know the background of you project or what you are trying to accomplish, but tableViews inside of tableVIew cells sounds unnecessarily trivial. Rather than using a master tableView with #slave tableViews, it would be cleaner to just break things out by section in a single tableView as stated in a previous answer. There are UITableViewDelegate methods designed to streamline this for you!
first you have to get string's height then the height have to give in below tableView delegate
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return stringHeight;
}
it is working for me.
I'm using Xcode 8.3.2 and Swift3.1.
I had the same requirement, have tried all, nothing worked for me.
Finally, UIStackView is what worked for me.
In a tableviewcell, I have added a UIStackView(Verticle), keep adding sub cells to that UIStackView. And it automatically increased the cell height.
Check the following to add UIStackView programmatically.
Add views in UIStackView programmatically
If you Use Different Sections and Rows use the below format, its working for me,
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
return 121;
}
if(indexPath.section==1)
{
return 81;
}
if (indexPath.section%2 == 0 && indexPath.row == 1) {
return 161;
}
if (indexPath.section%2 != 0 && indexPath.row == 0) {
return 81;
}
if (indexPath.section==16 && indexPath.row==0) {
return 161;
}
else
{
return 44;
}
}
i have Template code, different section and row, its each row have different sizes, so i have give this type of code, if you get idea see the above code then its helpful for you,
or
If you change the height for Content text size use the below code, its calculate the content size then change the height(UILabel) size, its working for me
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
ListModel *model = [ListArray objectAtIndex:indexPath.row];
CGRect labelRect = [model.content boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 90 - 15, 0)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : [UIFont fontWithName:#"Arial" size:14.0]
}
context:nil];
CGFloat heightOfCell = labelRect.size.height + 60;
if(heightOfCell > 106)
return heightOfCell;
return 106;
}
hope its helpful
yes of course you can have as many prototypes cells as you want for example check this piece of code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("TodayWeatherCell", forIndexPath: indexPath) as! SITodayWeatherTableViewCell
cell.setupCell(upCommingWeather)
cell.aboutCityUpdateTableViewClousure = {
self.tableView.reloadData()
}
return cell
}else {
let cell = tableView.dequeueReusableCellWithIdentifier("cityDetailCell", forIndexPath: indexPath) as! SICityDetailTableViewCell
let detail = detailCity[indexPath.row]
cell.setupCityDetail(detail)
return cell
// Configure the cell...
}
}
There are two different cells in one single UITableView.
Hope it helps.
I want to increase tableview cell and tableview height based on content.
Suppose tableview contain 2 record and his 1st cell height is 100 and 2nd cell height is 25 then tableview height should be 100+25=125.
How to achieve this functionality?
Thanks in advance.
You can definitely do that,
First make sure your constraint of cells subView must set to top to bottom in order to calculate the height required for the cell.
Make sure your delegated are set as below
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Set height constraint of your tableView and make outlet of that constraint.
Add below method to your class where you want to resize your tableView dynamically.
- (void)adjustHeightOfTableview
{
CGFloat height = self.tableView.contentSize.height;
//CGFloat maxHeight = self.tableView.superview.frame.size.height - self.tableView.frame.origin.y;
/*
Here you have to take care of two things, if there is only tableView on the screen then you have to see is your tableView going below screen using maxHeight and your screen height,
Or you can add your tableView inside scrollView so that your tableView can increase its height as much it requires based on the number of cell (with different height based on content) it has to display.
*/
// now set the height constraint accordingly
self.constraintHeightTableView.constant = height;
//If you want to increase tableView height with animation you can do that as below.
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
}
Call this method when you are ready with the dataSource for the table, and call the method as
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
//In my case i had to call this method after some delay, because (i think) it will allow tableView to reload completely and then calculate the height required for itself. (This might be a workaround, but it worked for me)
[self performSelector:#selector(adjustHeightOfTableview) withObject:nil afterDelay:0.3];
});
If you are running iOS 8+,
You can use:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80 // your desired or expected height
properties.
for this to take effect you should not have any height set in heightForRowAtIndexpath
You should set the cell constraints i.e., constraints for the elements present inside cell, so the set constraints are enough for the tableviewcell to calculate it's height in run time
Solution in swift 3
Step 1.
Set the top, bottom, leading and trailing constraints (do not make it constant height, but do set a constant height constraint as of now).
Now we gonna change this height dynamically based on the number of cells and its height.
Step 2.
Drag an outlet of that height constraint to the class, and copy this function anywhere in the class.
func adjustHeightOfTableview() {
let height: CGFloat = tableview.contentSize.height
self.outLetOfTheHeightConstraint.constant = height
}
Step 3.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
DispatchQueue.main.async {
self.tableview.reloadData()
self.perform(#selector(self.adjustHeightOfTableview))
}
}
self.tableView.frame = CGRectMake(self.tableView.origin.x, self.tableView.origin.y, self.tableView.frame.size.width, 125);
I have an UITableViewController that contains a custom cell. Each cell was created using a nib and contains a single non-scrollable UITextView. I have added constraints inside each cell so that the cell adapts its height to the content of the UITextView. So initially my controller looks like this :
Now I want that when the user types something in a cell its content automatically adapts. This question has been asked many times, see in particular this or the second answer here. I have thus written the following delegate in my code :
- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
[self.tableView beginUpdates];
[self.tableView endUpdates];
return YES;
}
However it leads to the following strange behavior : all constraints are ignored and all cells height collapse to the minimal value. See the picture below:
If I scroll down and up the tableView in order to force for a new call of cellForRowAtIndexPath, I recover the correct heights for the cells:
Note that I did not implement heightForRowAtIndexPath as I expect autoLayout to take care of this.
Could someone tell me what I did wrong or help me out here ? Thank you very much !
Here is a swift solution that is working fine for me. Provided you are using auto layout, you need assign a value to estimatedRowHeight and then return UITableViewAutomaticDimension for the row height. Finally do something similar to below in the text view delegate.
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 44.0
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// MARK: UITextViewDelegate
func textViewDidChange(textView: UITextView) {
// Calculate if the text view will change height, then only force
// the table to update if it does. Also disable animations to
// prevent "jankiness".
let startHeight = textView.frame.size.height
let calcHeight = textView.sizeThatFits(textView.frame.size).height //iOS 8+ only
if startHeight != calcHeight {
UIView.setAnimationsEnabled(false) // Disable animations
self.tableView.beginUpdates()
self.tableView.endUpdates()
// Might need to insert additional stuff here if scrolls
// table in an unexpected way. This scrolls to the bottom
// of the table. (Though you might need something more
// complicated if editing in the middle.)
let scrollTo = self.tableView.contentSize.height - self.tableView.frame.size.height
self.tableView.setContentOffset(CGPoint(x: 0, y: scrollTo), animated: false)
UIView.setAnimationsEnabled(true) // Re-enable animations.
}
My solution is similar to #atlwx but a bit shorter. Tested with static table. UIView.setAnimationsEnabled(false) is needed to prevent cell's contents "jumping" while table updates that cell's height
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 44.0
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func textViewDidChange(_ textView: UITextView) {
UIView.setAnimationsEnabled(false)
textView.sizeToFit()
self.tableView.beginUpdates()
self.tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
Tested on iOS 12
I really tried a lot of solutions and finally found a good one here
This works with animation and looks beautiful. The trick was the DispatchQueue.async block.
I also used TPKeyboardAvoidingTableView to make sure the keyboard doesn't overlap anything.
func textViewDidChange(_ textView: UITextView) {
// Animated height update
DispatchQueue.main.async {
self.tableView?.beginUpdates()
self.tableView?.endUpdates()
}
}
UPDATE
I got strange jumping issues because of TPKeyboardAvoidingTableView. Especially when I scrolled to the bottom and then a UITextView got active.
So I replaced TPKeyboardAvoidingTableView by native UITableView and handle the insets myself. The table view is does the scrolling natively.
The following example works for dynamic row height as the user types text into the cell. Even if you use auto layout you still have to implement the heightForRowAtIndexPath method. For this example to work constraints must be set to textView in such a way that if cell height increases textView will also grow in height. This can be achieved by adding a top constraint and bottom constraint from textView to cell content view. But do not set height constraint for textView itself. Also enable scrolling for the textView so that textView's content size will be updated as the user enters text. Then we use this content size to calculate the new row height. As long as the row height is long enough to vertically stretch the textView to equal to or greater than its content size the text view will not scroll even if scroll is enabled and that is what you need I believe.
In this example I have only a single row and I use only a single variable to keep track of the row height. But when we have multiple rows we need a variable for each row otherwise all the rows will have the same height. An array of rowHeight that corresponds to the tableView data source array may be used in that case.
#interface ViewController ()
#property (nonatomic, assign)CGFloat rowHeight;;
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.rowHeight = 60;
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell1"];
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.rowHeight;
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView {
[self.tableView beginUpdates];
CGFloat paddingForTextView = 40; //Padding varies depending on your cell design
self.rowHeight = textView.contentSize.height + paddingForTextView;
[self.tableView endUpdates];
}
#end
Using Swift 2.2 (earlier versions would likely work too), if you set the TableView to use auto dimensions (assuming you're working in a subclassed UITableViewController, like so:
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 50 // or something
You just need to implement the delegate in this file, UITextViewDelegate, and add the below function, and it should work. Just remember to set your textView's delegate to self (so, perhaps after you've dequeued the cell, cell.myTextView.delegate = self)
func textViewDidChange(textView: UITextView) {
self.tableView.beginUpdates()
textView.frame = CGRectMake(textView.frame.minX, textView.frame.minY, textView.frame.width, textView.contentSize.height + 40)
self.tableView.endUpdates()
}
Thanks to "Jose Tomy Joseph" for inspiring (enabling, really) this answer.
I've implemented a similar approach using a UITextView however to do so I had to implement heightForRowAtIndexPath
#pragma mark - SizingCell
- (USNTextViewTableViewCell *)sizingCell
{
if (!_sizingCell)
{
_sizingCell = [[USNTextViewTableViewCell alloc] initWithFrame:CGRectMake(0.0f,
0.0f,
self.tableView.frame.size.width,
0.0f)];
}
return _sizingCell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
self.sizingCell.textView.text = self.profileUpdate.bio;
[self.sizingCell setNeedsUpdateConstraints];
[self.sizingCell updateConstraintsIfNeeded];
[self.sizingCell setNeedsLayout];
[self.sizingCell layoutIfNeeded];
CGSize cellSize = [self.sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return cellSize.height;
}
sizingCell is an instance of the cell that is only used for sizing calculations.
What's important to note is that you need to attach the UITextView's upper and lower edge to the UITableViewCells contentView's upper and lower edge so that as the UITableViewCell changes in height the UITextView also changes in height.
For constraint layout I use a PureLayout (https://github.com/smileyborg/PureLayout) so the following constraint layout code may be unusual for you:
#pragma mark - Init
- (id)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style
reuseIdentifier:reuseIdentifier];
if (self)
{
[self.contentView addSubview:self.textView];
}
return self;
}
#pragma mark - AutoLayout
- (void)updateConstraints
{
[super updateConstraints];
/*-------------*/
[self.textView autoPinEdgeToSuperviewEdge:ALEdgeLeft
withInset:10.0f];
[self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop
withInset:5.0f];
[self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom
withInset:5.0f];
[self.textView autoSetDimension:ALDimensionWidth
toSize:200.0f];
}
Inspired by the two previous answers, I found a way to solve my problem. I think the fact that I had a UITextView was causing some troubles with autoLayout. I added the following two functions to my original code.
- (CGFloat)textViewHeightForAttributedText: (NSAttributedString*)text andWidth: (CGFloat)width {
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UIFont *font = [UIFont systemFontOfSize:14.0];
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.sampleStrings[indexPath.row] attributes:attrsDictionary];
return [self textViewHeightForAttributedText:attrString andWidth:CGRectGetWidth(self.tableView.bounds)-31]+20;
}
where in the last line 31 is the sum of my constraints to the left and right sides of the cell and 20 is just some arbitrary slack.
I found this solution while reading this this very interesting answer.
The trick to immediately update the tableview cells height in a smooth way without dismissing the keyboard is to run the following snippet to be called in the textViewDidChange event after you set the size of the textView or other contents you have in the cell:
[tableView beginUpdates];
[tableView endUpdates];
However this will may not be enough. You should also make sure the tableView has enough elasticity to keep the same contentOffset. You get that elasticity by setting the tableView contentInset bottom. I suggest this elasticity value to be at least the maximum distance you need from the bottom of the last cell to the bottom of the tableView. For instance, it could be the height of the keyboard.
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0);
For more details and some useful extra features around this matter please check out the following link:
Resize and move UITableViewCell smoothly without dismissing keyboard
The solution almost everyone suggested is the way to go, I will add only a minor improvement. As a recap:
Simply set the estimated height, I do it via storyboard:
Make sure you have the constraints for the UITextView correctly set within the cell.
In the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I simply call:
cell.myTextView.sizeToFit()
Previously beginUpdates/endUpdates were the advertised solution.
Since iOS 11, performBatchUpdates is what has been recommended source.
Calling performBatchUpdates after making a change to a cell's content works for me.
Check out the Objective C solution I have provided in the following link below.
Simple to implement, clean, and no need for auto layout. No constraints needed. Tested in iOS10 and iOS11.
Resize and move UITableViewCell smoothly without dismissing keyboard
I have one UItableview and I have one customCell for printing the data in the UITable.
Now, I want to put more data in specific cell. When I select on cell, it should increase the height of cell and put that data. How is it possible.?
In DidSelect make the height of particular selected cell to your required cell.
And reload the table.
This is helpful : How to programmatically increase UITableView cell's height in iPhone?
Call reload table data by changing the parameters of row and to find the height of a cell after adding data use:
CGSize size=[youstring sizeWithFont:yourlabel.font constrainedToSize:labelfontsize lineBreakMode:lineBreakmodestyle];
And get height from size.height and return the height in heightForRowAtIndexPath method while reloading tableview.
what you should do is First Calculate the Size of your content for which you have to increase the cell height
//Suppose here is your string which you want to show
-(float) calculateHeight:(NSString *)dataStr
{
CGSize size = [dataStr sizeWithFont:[UIFont fontWithName:#"Helvetica" size:17] constrainedToSize:CGSizeMake(280, 999) lineBreakMode:UILineBreakModeWordWrap];
NSLog(#"%f",size.height);
return size.height + 10;
}
This Above function will return the height according to your Text Size.
Now just have to call this function on the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
delegete and reload the tableview and give the size you calculated with the above function at heightForRowAtIndexPath delegete.
Try this sample if I've understood what you want: https://github.com/Dmitriy837/changeTableRowHeighWhenTaped
I suggest you subclass UITableView and support additional property with cell heights in it and change the cell's height when it's taped.
I have a UITableView that is populated with custom cells (inherited from UITableViewCell), each cell contains a UIWebView that is automatically resize based on it's contents. Here's the thing, how can I change the height of the UITableView cells based on their content (variable webView).
The solution must be dynamic since the HTML used to populate the UIWebViews is parsed from an ever changing feed.
I have a feeling I need to use the UITableView delegate method heightForRowAtIndexPath but from it's definition:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
;//This needs to be variable
}
I can't access the cell or it's contents. Can I change the height of the cell in cellForRowAtIndexPath?
Any help would be grand. Thanks.
Note
I asked this question over 2 years ago. With the intro of auto layout the best solution for iOS7 can be found:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
and on iOS8 this functionality is built in the SDK
This usually works pretty well:
Objective-C:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
return UITableViewAutomaticDimension;
}
The best way that I've found for dynamic height is to calculate the height beforehand and store it in a collection of some sort (probably an array.) Assuming the cell contains mostly text, you can use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:] to calculate the height, and then return the corresponding value in heightForRowAtIndexPath:
If the content is constantly changing, you could implement a method that updated the array of heights when new data was provided.
self.tblVIew.estimatedRowHeight = 500.0; // put max you expect here.
self.tblVIew.rowHeight = UITableViewAutomaticDimension;
I tried many solutions, but the one that worked was this, suggested by a friend:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int height = [StringUtils findHeightForText:yourLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:17.0f]];
height += [StringUtils findHeightForText:yourOtherLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:14.0f]];
return height + CELL_SIZE_WITHOUT_LABELS; //important to know the size of your custom cell without the height of the variable labels
}
The StringUtils.h class:
#import <Foundation/Foundation.h>
#interface StringUtils : NSObject
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font;
#end
StringUtils.m class:
#import "StringUtils.h"
#implementation StringUtils
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGFloat result = font.pointSize+4;
if (text) {
CGSize size;
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
result = MAX(size.height, result); //At least one row
}
return result;
}
#end
It worked perfectly for me. I had a Custom Cell with 3 images with fixed sizes, 2 labels with fixed sizes and 2 variable labels.
The big problem with cells with dynamic height in iOS is that the table vc must calculate and return a height of each cell before the cells are drawn. Before a cell is drawn, though, it doesn't have a frame and thus no width. This causes a problem if your cell is to change its height based on, say, the amount of text in the textLabel, since you do not know its width.
A common solution that I've seen is that people define a numeric value for the cell width. This is a bad approach, since tables can be plain or grouped, use iOS 7 or iOS 6 styling, be displayed on an iPhone or iPad, in landscape or portrait mode etc.
I struggled with these issues in an iOS app of mine, which supports iOS5+ and both iPhone and iPad with multiple orientations. I needed a convenient way to automate this and leave the logic out of the view controller. The result became a UITableViewController sub class (so that it can hold state) that supports default cells (Default and Subtitle style) as well as custom cells.
You can grab it at GitHub (https://github.com/danielsaidi/AutoSizeTableView). I hope it helps those of you who still struggle with this problem. If you do check it out, I'd love to hear what you think and if it worked out for you.
Here is code that I used for dynamic cell height when fetching tweets from twitter and then storing them in CoreData for offline reading.
Not only does this show how to get the cell and data content, but also how to dynamically size a UILabel to the content with padding
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Tweet *tweet = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* text = tweet.Text;
TweetTableViewCell *cell = (TweetTableViewCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
//Set the maximum size
CGSize maximumLabelSize = cell.tweetLabel.frame.size;
CGPoint originalLocation = cell.tweetLabel.frame.origin;
//Calculate the new size based on the text
CGSize expectedLabelSize = [text sizeWithFont:cell.tweetLabel.font constrainedToSize:maximumLabelSize lineBreakMode:cell.tweetLabel.lineBreakMode];
//Dynamically figure out the padding for the cell
CGFloat topPadding = cell.tweetLabel.frame.origin.y - cell.frame.origin.y;
CGFloat bottomOfLabel = cell.tweetLabel.frame.origin.y + cell.tweetLabel.frame.size.height;
CGFloat bottomPadding = cell.frame.size.height - bottomOfLabel;
CGFloat padding = topPadding + bottomPadding;
CGFloat topPaddingForImage = cell.profileImage.frame.origin.y - cell.frame.origin.y;
CGFloat minimumHeight = cell.profileImage.frame.size.height + topPaddingForImage + bottomPadding;
//adjust to the new size
cell.tweetLabel.frame = CGRectMake(originalLocation.x, originalLocation.y, cell.tweetLabel.frame.size.width, expectedLabelSize.height);
CGFloat cellHeight = expectedLabelSize.height + padding;
if (cellHeight < minimumHeight) {
cellHeight = minimumHeight;
}
return cellHeight;
}
Also i think such an algorithm will suit you:
1) in cellForrowAtIndexPath you activate your webviews for loading and give them tags equal to indexPath.row
2) in webViewDidFinishLoading you calculate the height of the content in the cell, and compose a dictionary with keys and values like this: key= indexPath.row value = height
3)call [tableview reloadData]
4) in [tableview cellForRowAtIndexPath:indexPath] set proper heights for corresponding cells
This is one of my nice solution. it's worked for me.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell.textLabel.text = [_nameArray objectAtIndex:indexPath.row];
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
We need to apply these 2 changes.
1)cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
2)return UITableViewAutomaticDimension;
In Swift 4+ you can set it dinamic
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
I always implement this in all my cells in a super cell class because for some reason UITableViewAutomaticDimension doesn't work so well.
-(CGFloat)cellHeightWithData:(id)data{
CGFloat height = [[self contentView] systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[self fillCellWithData:data]; //set the label's text or anything that may affect the size of the cell
[self layoutIfNeeded];
height = [[self contentView] systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height+1; //must add one because of the cell separator
}
just call this method on your -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPathusing a dummy cell.
note: this works only with autolayout, but it also works with ios 7 and later.
pd: don't forget to check the checkbox on the xib or storyboard for "preferred width explicit" and set the static width (on the cmd + alt + 5 menu)
Swift
Use custom cell and labels. Set up the constrains for the UILabel. (top, left, bottom, right) Set lines of the UILabel to 0
Add the following code in the viewDidLoad method of the ViewController:
tableView.estimatedRowHeight = 68.0
tableView.rowHeight = UITableViewAutomaticDimension
// Delegate & data source
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
I had very large test in UILabel. Above all fail to work, then i create category for string as below and got the exact height
- (CGFloat)heightStringWithEmojifontType:(UIFont *)uiFont ForWidth:(CGFloat)width {
// Get text
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef) self );
CFIndex stringLength = CFStringGetLength((CFStringRef) attrString);
// Change font
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, stringLength), kCTFontAttributeName, ctFont);
// Calc the size
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CFRange fitRange;
CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(width, CGFLOAT_MAX), &fitRange);
CFRelease(ctFont);
CFRelease(framesetter);
CFRelease(attrString);
return frameSize.height + 10;}