64

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
  • 957
  • 2
  • 9
  • 16
  • sure, if you have access to the tableview, number of rows, height of the row etc. All you need to do is [otherVC.tableView setFrame:CGRectMake(originX,originY,width,height)]; – Srikanth Jan 08 '13 at 21:04
  • @Srikanth Correct, but that only works if you're not using autolayout. If using autolayout, you have to change your view's constraints. – Rob Jan 08 '13 at 22:11
  • 1
    Alex, are you using autolayout (which only works on effective iOS 6)? In answer to your question, you can't have it automatically adjust the height of the tableview without doing so programmatically yourself. The solution just differs a little depending upon whether you're using iOS 6's autolayout or not. – Rob Jan 08 '13 at 22:22
  • This code raises a number of questions which fall beyond the purview of acceptable comments (as it's highly localized, specific to this particular snippet of code). So, I've created a chat: http://chat.stackoverflow.com/rooms/22461/change-uitableview-height-dynamically – Rob Jan 09 '13 at 13:34

8 Answers8

173

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 4
    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 – alex Jan 09 '13 at 05:32
  • 1
    @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. – Rob Jan 09 '13 at 08:25
  • I tried you answer for autolayout off but unfortunately my table view is not displayed at all :( – Dejell Oct 24 '13 at 05:11
  • @Dejel: Check that you're doing the [self adjustHeightOfTableView] in viewDidAppear and not in viewDidLoad. – Tiago Martins Dec 07 '13 at 16:20
  • 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. – Leon Storey Mar 21 '14 at 14:33
  • I cannot upvote this enough. Well done on this answer. – wiznaibus Apr 01 '14 at 13:50
  • 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) – Alex May 05 '14 at 02:24
  • @Alex You could presumably hide it before resizing (either by changing the default `frame` or `alpha`). Or try doing it in `viewWillLayoutSubviews`. – Rob May 05 '14 at 03:03
  • @Rob I tried to do it in `viewWillLayoutSubviews` already, but I'm getting an error. Made a question about it here http://stackoverflow.com/questions/23464303/change-height-constraint-of-table-view-outside-of-viewdidappear-in-ios-7 Any idea how to make that work? – Alex May 05 '14 at 03:29
  • @Rob thanks for including the autolayout version. would have been stuck without that :) – Allen Jun 09 '14 at 22:39
  • @Rob What an answer! Thanks, you saved me a lot of work. – Tony J Stark Jul 03 '14 at 15:00
  • I have some difficulties in making the right TableView constraints inside the ScrollView with AutoLayout, adjusting the constraint height programatically solved the problem! – Ricky Sep 08 '14 at 07:13
  • @Rob Your answer (especially Auto Layout version) saved me from going insane :) – westmark Oct 02 '14 at 10:08
21

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;}
Box Box Box Box
  • 5,094
  • 10
  • 49
  • 67
Mohit Tomar
  • 5,173
  • 2
  • 33
  • 40
10

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().

LimeRed
  • 1,278
  • 13
  • 18
  • this is good. to be more complete it could also handle the max value. – matt bezark Feb 03 '17 at 20:36
  • 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. – LimeRed Feb 13 '17 at 06:25
  • Your code didn't work for me I got the error : " Argument labels '(_:, _:)' do not match to any available overloads – Badr Filali Apr 05 '17 at 09:59
  • Was using a custom extension for CGSize initialization. Edited my answer to use the default initialization. Please try again ;) – LimeRed Apr 06 '17 at 02:31
  • No idea why this is working for me. i thought I learned about view life cycles. Apparently not. Good find. – Rishab Jan 18 '18 at 07:51
  • 2
    Awesome answer! I have been searching for this solution for days. Wish I could upvote 5 or 6 times. – wildbagel Mar 06 '18 at 18:10
  • 2
    Possible improvement: `height: self.contentSize.height + self.contentInset.top + self.contentInset.bottom` – Cœur Jul 24 '18 at 11:56
8

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
    }
Justin Vallely
  • 5,932
  • 3
  • 30
  • 44
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.

benka
  • 4,732
  • 35
  • 47
  • 58
Csaba
  • 104
  • 1
  • 7
2

for resizing my table I went with this solution in my tableview controller witch is perfectly fine:

[objectManager getObjectsAtPath:self.searchURLString
                         parameters:nil
                            success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                NSArray* results = [mappingResult array];
                                self.eventArray = results;
                                NSLog(@"Events number at first: %i", [self.eventArray count]);
                                CGRect newFrame = self.activityFeedTableView.frame;
                                newFrame.size.height = self.cellsHeight + 30.0;
                                self.activityFeedTableView.frame = newFrame;

                                self.cellsHeight = 0.0;

                            }
                            failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
                                                                                message:[error localizedDescription]
                                                                               delegate:nil
                                                                      cancelButtonTitle:@"OK"
                                                                      otherButtonTitles:nil];
                                [alert show];
                                NSLog(@"Hit error: %@", error);
                            }];

The resizing part is in a method but here is just so you can see it. Now the only problem I haveis resizing the scroll view in the other view controller as I have no idea when the tableview has finished resizing. At the moment I'm doing it with performSelector: afterDelay: but this is really not a good way to do it. Any ideas?

alex
  • 957
  • 2
  • 9
  • 16
2

Use simple and easy code

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        let myCell = tableView.dequeueReusableCellWithIdentifier("mannaCustumCell") as! CustomCell
        let heightForCell = myCell.bounds.size.height;

        return heightForCell;
    }
Maninderjit Singh
  • 1,419
  • 1
  • 13
  • 23
0

I found adding constraint programmatically much easier than in storyboard.

    var leadingMargin = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.LeadingMargin, relatedBy: NSLayoutRelation.Equal, toItem: self.mView, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1, constant: 0.0)

    var trailingMargin = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.TrailingMargin, relatedBy: NSLayoutRelation.Equal, toItem: mView, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1, constant: 0.0)

    var height = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenSize.height - 55)

    var bottom = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.BottomMargin, relatedBy: NSLayoutRelation.Equal, toItem: self.mView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1, constant: screenSize.height - 200)

    var top = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.TopMargin, relatedBy: NSLayoutRelation.Equal, toItem: self.mView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1, constant: 250)

    self.view.addConstraint(leadingMargin)
    self.view.addConstraint(trailingMargin)
    self.view.addConstraint(height)
    self.view.addConstraint(bottom)
    self.view.addConstraint(top)
Prajyot
  • 99
  • 1
  • 12