Change UITableView height dynamically

120,835

Solution 1

There isn't a system feature to change the height of the table based upon the contents of the tableview. Having said that, it is possible to programmatically change the height of the tableview based upon the contents, specifically based upon the contentSize of the tableview (which is easier than manually calculating the height yourself). A few of the particulars vary depending upon whether you're using the new autolayout that's part of iOS 6, or not.

But assuming you're configuring your table view's underlying model in viewDidLoad, if you want to then adjust the height of the tableview, you can do this in viewDidAppear:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self adjustHeightOfTableview];
}

Likewise, if you ever perform a reloadData (or otherwise add or remove rows) for a tableview, you'd want to make sure that you also manually call adjustHeightOfTableView there, too, e.g.:

- (IBAction)onPressButton:(id)sender
{
    [self buildModel];
    [self.tableView reloadData];

    [self adjustHeightOfTableview];
}

So the question is what should our adjustHeightOfTableview do. Unfortunately, this is a function of whether you use the iOS 6 autolayout or not. You can determine if you have autolayout turned on by opening your storyboard or NIB and go to the "File Inspector" (e.g. press option+command+1 or click on that first tab on the panel on the right):

enter image description here

Let's assume for a second that autolayout was off. In that case, it's quite simple and adjustHeightOfTableview would just adjust the frame of the tableview:

- (void)adjustHeightOfTableview
{
    CGFloat height = self.tableView.contentSize.height;
    CGFloat maxHeight = self.tableView.superview.frame.size.height - self.tableView.frame.origin.y;

    // if the height of the content is greater than the maxHeight of
    // total space on the screen, limit the height to the size of the
    // superview.

    if (height > maxHeight)
        height = maxHeight;

    // now set the frame accordingly

    [UIView animateWithDuration:0.25 animations:^{
        CGRect frame = self.tableView.frame;
        frame.size.height = height;
        self.tableView.frame = frame;

        // if you have other controls that should be resized/moved to accommodate
        // the resized tableview, do that here, too
    }];
}

If your autolayout was on, though, adjustHeightOfTableview would adjust a height constraint for your tableview:

- (void)adjustHeightOfTableview
{
    CGFloat height = self.tableView.contentSize.height;
    CGFloat maxHeight = self.tableView.superview.frame.size.height - self.tableView.frame.origin.y;

    // if the height of the content is greater than the maxHeight of
    // total space on the screen, limit the height to the size of the
    // superview.

    if (height > maxHeight)
        height = maxHeight;

    // now set the height constraint accordingly

    [UIView animateWithDuration:0.25 animations:^{
        self.tableViewHeightConstraint.constant = height;
        [self.view setNeedsUpdateConstraints];
    }];
}

For this latter constraint-based solution to work with autolayout, we must take care of a few things first:

  1. Make sure your tableview has a height constraint by clicking on the center button in the group of buttons here and then choose to add the height constraint:

    add height constraint

  2. Then add an IBOutlet for that constraint:

    add IBOutlet

  3. Make sure you adjust other constraints so they don't conflict if you adjust the size tableview programmatically. In my example, the tableview had a trailing space constraint that locked it to the bottom of the screen, so I had to adjust that constraint so that rather than being locked at a particular size, it could be greater or equal to a value, and with a lower priority, so that the height and top of the tableview would rule the day:

    adjust other constraints

    What you do here with other constraints will depend entirely upon what other controls you have on your screen below the tableview. As always, dealing with constraints is a little awkward, but it definitely works, though the specifics in your situation depend entirely upon what else you have on the scene. But hopefully you get the idea. Bottom line, with autolayout, make sure to adjust your other constraints (if any) to be flexible to account for the changing tableview height.

As you can see, it's much easier to programmatically adjust the height of a tableview if you're not using autolayout, but in case you are, I present both alternatives.

Solution 2

create your cell by xib or storyboard. give it's outlet's contents. now call it in CellForRowAtIndexPath. eg. if you want to set cell height according to Comment's label text. enter image description here

so set you commentsLbl.numberOfLine=0;enter image description here

so set you commentsLbl.numberOfLine=0;

then in ViewDidLoad

 self.table.estimatedRowHeight = 44.0 ;
self.table.rowHeight = UITableViewAutomaticDimension;

and now

-(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;}

Solution 3

Lots of the answers here don't honor changes of the table or are way too complicated. Using a subclass of UITableView that will properly set intrinsicContentSize is a far easier solution when using autolayout. No height constraints etc. needed.

class UIDynamicTableView: UITableView
{
    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: self.contentSize.height)
    }

    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
    }
} 

Set the class of your TableView to UIDynamicTableView in the interface builder and watch the magic as this TableView will change it's size after a call to reloadData().

Solution 4

This can be massively simplified with just 1 line of code in viewDidAppear:

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        tableViewHeightConstraint.constant = tableView.contentSize.height
    }

Solution 5

Rob's solution is very nice, only thing that in his -(void)adjustHeightOfTableview method the calling of

[self.view needsUpdateConstraints]

does nothing, it just returns a flag, instead calling

[self.view setNeedsUpdateConstraints]

will make the desired effect.

Share:
120,835
alex
Author by

alex

Updated on April 06, 2020

Comments

  • alex
    alex about 4 years

    I want to change the height of my tableview from another viewcontroller based on the sum of its cells' heights, as they are dynamic. Is it at all possible? Thanks

    Add-on:

    What I basically have is a UserProfileViewController that has a containerview on half of the screen. There I add different other viewcontrollers:

    enter image description here

    enter image description here

    In the case of the wall button this is how I add the viewcontroller and it's subsequent tableview:

    - (IBAction)wallButtonPressed:(id)sender
    {
        //Check if there is an instance of the viewcontroller we want to display. If not make one and set it's tableview frame to the container's view bounds
        if(!_userWallViewController) {
            self.userWallViewController = [[WallViewController alloc] init];
    //        self.userWallViewController.activityFeedTableView.frame = self.containerView.bounds;
    
        }
    
        [self.userWallViewController.containerView addSubview:self.userWallViewController.activityFeedTableView];
        //If the currentviewcontroller adn it's view are already added to the hierarchy remove them
        [self.currentViewController.view removeFromSuperview];
        [self.currentViewController removeFromParentViewController];
    
        //Add the desired viewcontroller to the currentviewcontroller
        self.currentViewController = self.userWallViewController;
    
        //Pass the data needed for the desired viewcontroller to it's instances
        self.userWallViewController.searchURLString = [NSString stringWithFormat:@"event/user/%@/", self.userID];
        self.userWallViewController.sendCommentURLString = [NSString stringWithFormat:@"event/message/%@", self.userID];
    
        [self.userWallViewController.activityFeedTableView reloadData];
    
        self.userWallViewController.totalCellHeight = ^(float totalCellHeight){
    
            self.scrollView.contentSize = CGSizeMake(320.0, totalCellHeight);
            CGRect newFrame = self.userWallViewController.containerView.frame;
            newFrame.size.height = totalCellHeight + 33.0;
            self.userWallViewController.containerView.frame = newFrame;
    
            self.userWallViewController.activityFeedTableView.frame = self.containerView.bounds;
        };
    
        //Add this containerview to the desired viewcontroller's containerView
        self.userWallViewController.containerView = self.containerView;
    
    
        //Add the needed viewcontroller and view to the parent viewcontroller and the containerview
        [self addChildViewController:self.userWallViewController];
        [self.containerView addSubview:self.userWallViewController.view];
    
        //CLEAN UP THE CONTAINER VIEW BY REMOVING THE PREVIOUS ADDED TABLE VIEWS
        [self.userFansViewController.userSimpleTableView removeFromSuperview];
        [self.fanOfViewController.userSimpleTableView removeFromSuperview];
        [self.userPublishedMovellaListViewController.gridView removeFromSuperview];
    
        [self.userPublishedMovellaListViewController removeFromParentViewController];
    
        self.userPublishedMovellaListViewController = nil;
    }
    

    and in that viewcontroller this is where I initialize my tableview:

    -(UITableView *)activityFeedTableView
    {
        if (!_activityFeedTableView) {
            _activityFeedTableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 850.0) style:UITableViewStylePlain];
        }
        return _activityFeedTableView;
    }
    

    I am calculating he total sum of the cell's height, the problem is that the cell's height method is called way after the getter of te tableview is called. So I would need some sort of way to know when the cells' height method is done for all cells and then I can resize my tableview. Thanks

  • alex
    alex over 11 years
    wow. that must be the best structured answer I've ever received. Much appreciate that. The thing is my app also supports ios 5 so I can't really use that feature even though I'm glad I've learned something out of your answer. I have updated my question. Can you please take a look. Thank you
  • Rob
    Rob over 11 years
    @alex Well, the answer is all the same, but just without all of that constraint silliness. Just use the first rendition of the adjustHeightOfTableView and you should be good.
  • Dejell
    Dejell over 10 years
    I tried you answer for autolayout off but unfortunately my table view is not displayed at all :(
  • Tiago Martins
    Tiago Martins over 10 years
    @Dejel: Check that you're doing the [self adjustHeightOfTableView] in viewDidAppear and not in viewDidLoad.
  • Leon Storey
    Leon Storey over 10 years
    Only just upgrading our apps to AutoLayout (dropping support for iOS5) and the new NSLayoutConstraint is, once setup, a very nice alternative to setting frames.
  • wiznaibus
    wiznaibus about 10 years
    I cannot upvote this enough. Well done on this answer.
  • Alex
    Alex about 10 years
    One additional question to your answer: I have one table view that has a dynamic number of cells. I'm using the above method to modify the height constraint and it works fine. I would like to make an improvement though and that is to show the table view only after the height constraint has been modified (therefore not in viewDidAppear, where you can see the height being changed). Any ideas on that? I need this to work on iOS 7.0 and 7.1 (with Auto Layout, of course)
  • Rob
    Rob about 10 years
    @Alex You could presumably hide it before resizing (either by changing the default frame or alpha). Or try doing it in viewWillLayoutSubviews.
  • Alex
    Alex about 10 years
    @Rob I tried to do it in viewWillLayoutSubviews already, but I'm getting an error. Made a question about it here stackoverflow.com/questions/23464303/… Any idea how to make that work?
  • Allen
    Allen about 10 years
    @Rob thanks for including the autolayout version. would have been stuck without that :)
  • Tony J Stark
    Tony J Stark almost 10 years
    @Rob What an answer! Thanks, you saved me a lot of work.
  • Ricky
    Ricky almost 10 years
    I have some difficulties in making the right TableView constraints inside the ScrollView with AutoLayout, adjusting the constraint height programatically solved the problem!
  • westmark
    westmark over 9 years
    @Rob Your answer (especially Auto Layout version) saved me from going insane :)
  • Sakthimuthiah
    Sakthimuthiah almost 9 years
    Thanks, it got worked. After implementing this answer, i had added " override func viewDidAppear(animated: Bool) { tableView.reloadData() } " this code from the following link "appcoda.com/self-sizing-cells" - Then i got resized row in cell
  • János
    János almost 9 years
    what about UITextView there is no numberOfLine property
  • Fa.Shapouri
    Fa.Shapouri almost 9 years
    @János UITextView Adjust height automatically, If you fixed size, the scroll in textView going to be enabled.
  • ReyJenald
    ReyJenald over 8 years
    Thanks. this saved me hours.
  • matt bezark
    matt bezark over 7 years
    this is good. to be more complete it could also handle the max value.
  • LimeRed
    LimeRed over 7 years
    Thanks. I would prefer setting a max value with a lessOrEqual height constraint. This way the class stays very simple, and you can set the maximum easy from the interface builder. intrinsicContentSize would be used unless it exceeds your lessOrEqual constraint. Then scrolling would be enabled.
  • Badr Filali
    Badr Filali about 7 years
    Your code didn't work for me I got the error : " Argument labels '(_:, _:)' do not match to any available overloads
  • LimeRed
    LimeRed about 7 years
    Was using a custom extension for CGSize initialization. Edited my answer to use the default initialization. Please try again ;)
  • Maninderjit Singh
    Maninderjit Singh about 7 years
    Amazing job @Mohittomar
  • Rishab
    Rishab over 6 years
    No idea why this is working for me. i thought I learned about view life cycles. Apparently not. Good find.
  • wildbagel
    wildbagel over 6 years
    Awesome answer! I have been searching for this solution for days. Wish I could upvote 5 or 6 times.
  • Cœur
    Cœur almost 6 years
    Possible improvement: height: self.contentSize.height + self.contentInset.top + self.contentInset.bottom
  • droid
    droid almost 3 years
    This is a good answer if you don't have text or view below comment label