Resizing UITableView to fit content

IosObjective CUitableviewSizetofit

Ios Problem Overview


I am creating an app which will have a question in a UILabel and a multiple choice answers displayed in UITableView, each row showing a multiple choice. Questions and answers will vary, so I need this UITableView to be dynamic in height.

I would like to find a sizeToFit work around for the table. Where the table's frame is set to the height of all it's content.

Can anyone advise on how I can achieve this?

Ios Solutions


Solution 1 - Ios

Swift 5 and 4.2 solution without KVO, DispatchQueue, or setting constraints yourself.

This solution is based on Gulz's answer.

  1. Create a subclass of UITableView:

    import UIKit

    final class ContentSizedTableView: UITableView { override var contentSize:CGSize { didSet { invalidateIntrinsicContentSize() } }

     override var intrinsicContentSize: CGSize {
         layoutIfNeeded()
         return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
     }
    

    }

  2. Add a UITableView to your layout and set constraints on all sides. Set the class of it to ContentSizedTableView.

  3. You should see some errors, because Storyboard doesn't take our subclass' intrinsicContentSize into account. Fix this by opening the size inspector and overriding the intrinsicContentSize to a placeholder value. This is an override for design time. At runtime it will use the override in our ContentSizedTableView class


Update: Changed code for Swift 4.2. If you're using a prior version, use UIViewNoIntrinsicMetric instead of UIView.noIntrinsicMetric

Solution 2 - Ios

Actually I found the answer myself.

I just create a new CGRect for the tableView.frame with the height of table.contentSize.height

That sets the height of the UITableView to the height of its content. Since the code modifies the UI, do not forget to run it in the main thread:

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });

Solution 3 - Ios

Swift Solution

Follow these steps:

  1. Set the height constraint for the table from the storyboard.

  2. Drag the height constraint from the storyboard and create @IBOutlet for it in the view controller file.

    @IBOutlet var tableHeight: NSLayoutConstraint!
    
  3. Then you can change the height for the table dynamicaly using this code:

    override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.tableHeight?.constant = self.table.contentSize.height
    }
    

If the last row is cut off, try to call viewWillLayoutSubviews() in willDisplay cell function:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

Solution 4 - Ios

I've tried this in iOS 7 and it worked for me

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}

Solution 5 - Ios

Add an observer for the contentSize property on the table view, and adjust the frame size accordingly

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

then in the callback:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

Hope this will help you.

Solution 6 - Ios

I had a table view inside scroll view and had to calculate tableView's height and resize it accordingly. Those are steps I've taken:

  1. add a UIView to your scrollView (probably will work without this step but i did it to avoid any possible conflicts) - this will be a containr view for your table view. If you take this step , then set the views borders right to tableview's ones.

  2. create a subclass of UITableView:

    class IntrinsicTableView: UITableView {

     override var contentSize:CGSize {
         didSet {
             self.invalidateIntrinsicContentSize()
         }
     }
    
     override var intrinsicContentSize: CGSize {
         self.layoutIfNeeded()
         return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
     }
    

    }

  3. set class of a table view in Storyboard to IntrinsicTableView: screenshot: http://joxi.ru/a2XEENpsyBWq0A

  4. Set the heightConstraint to your table view

  5. drag the IBoutlet of your table to your ViewController

  6. drag the IBoutlet of your table's height constraint to your ViewController

  7. add this method into your ViewController:

    override func viewWillLayoutSubviews() { super.updateViewConstraints() self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height }

Hope this helps

Solution 7 - Ios

In case you don't want to track table view's content size changes yourself, you might find this subclass useful.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {
    
    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }
            
            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }
    
    override var intrinsicContentSize: CGSize {
        return contentSize
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}

Solution 8 - Ios

Swift 5 Solution

Follow these four steps:

  1. Set the height constraint for the tableview from the storyboard.

  2. Drag the height constraint from the storyboard and create @IBOutlet for it in the view controller file.

    @IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
    
  3. Add an observer for the contentSize property on the override func viewDidLoad()

override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
 
    }

  1. Then you can change the height for the table dynamicaly using this code:

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
         if(keyPath == "contentSize"){
             if let newvalue = change?[.newKey]
             {
                 DispatchQueue.main.async {
                 let newsize  = newvalue as! CGSize
                 self.tableViewHeightConstraint.constant = newsize.height
                 }
                 
             }
         }
     }
    

Solution 9 - Ios

In case your contentSize is not correct this is because it is based on the estimatedRowHeight (automatic), use this before

> tableView.estimatedRowHeight = 0;

source : https://forums.developer.apple.com/thread/81895

Solution 10 - Ios

Swift 3, iOS 10.3

Solution 1: Just put self.tableview.sizeToFit() in cellForRowAt indexPath function. Make sure to set tableview height higher then you need. This is a good solution if you don't have views below tableview. However, if you have, bottom tableview constraint will not be updated (I didn't try to fix it because I came up with solution 2)

Example:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }
    
    return UITableViewCell()
}

Solution 2: Set tableview height constraint in storyboard and drag it to the ViewController. If you know the average height of your cell and you know how many elements your array contains, you can do something like this:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*EDIT:

After all the solutions I tried and every of them was fixing something, but not completely, this is the answer that explains and fixes this problem completely.

Solution 11 - Ios

I did in a bit different way, Actually my TableView was inside scrollview so i had to give height constraint as 0.

Then at runtime I made following changes,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }

Solution 12 - Ios

This works for me using Auto Layout, with a table view with only one section.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

I call this function when setting up Auto Layout (The sample here uses SnapKit, but you get the idea):

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

I want the UITableView only to be as tall as the combined height of the cells; I loop through the cells and accumulate the total height of the cells. Since the size of the table view is CGRect.zero at this point, I need to set the bounds to be able to respect the Auto Layout rules defined by the cell. I set the size to an arbitrary value that should be large enough. The actual size will be calculated later by the Auto Layout system.

Solution 13 - Ios

There is a much better way to do it if you use AutoLayout: change the constraint that determines the height. Just calculate the height of your table contents, then find the constraint and change it. Here's an example (assuming that the constraint that determines your table's height is actually a height constraint with relation "Equal"):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}

Solution 14 - Ios

Mimo's answer and Anooj VM 's answer both are awesome but there is a small problem if you have a large list, it's possible that the height of the frame will cutoff some of your cells.

So. I have modified the answer a little bit:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}

Solution 15 - Ios

My Swift 5 implementation is to set the hight constraint of the tableView to the size of its content (contentSize.height). This method assumes you are using auto layout. This code should be placed inside the cellForRowAt tableView method.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

Solution 16 - Ios

based on fl034's answer

SWift 5

var tableViewHeight: NSLayoutConstraint?

    tableViewHeight = NSLayoutConstraint(item: servicesTableView, 
    attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,
    multiplier: 0.0, constant: 10)
    tableViewHeight?.isActive = true


  func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    tableViewHeight?.constant = tableView.contentSize.height
    tableView.layoutIfNeeded()
}

Solution 17 - Ios

As an extension of Anooj VM's answer, I suggest the following to refresh content size only when it changes.

This approach also disable scrolling properly and support larger lists and rotation. There is no need to dispatch_async because contentSize changes are dispatched on main thread.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}

        
- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}
        
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}
        
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }
        
- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}

Solution 18 - Ios

objc version of Musa almatri

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}

Solution 19 - Ios

You can try Out this Custom AGTableView

To Set a TableView Height Constraint Using storyboard or programmatically. (This class automatically fetch a height constraint and set content view height to yourtableview height).

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!
    
    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }
    
    func associateConstraints() {
        // iterate through height constraints and identify
        
        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Note If any problem to set a Height then yourTableView.layoutSubviews().

Solution 20 - Ios

Based on answer of fl034. But for Xamarin.iOS users:

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }

Solution 21 - Ios

I am using a UIView extension , approach is close to @ChrisB approach above

 extension UIView {
func updateHeight(_ height:NSLayoutConstraint)
{
    
    let newSize = CGSize(width: self.frame.size.width, height: CGFloat(MAXFLOAT))
    let fitSize : CGSize = self.sizeThatFits(newSize)
    
    height.constant = fitSize.height
    
   
}
}

implementation : :

@IBOutlet weak var myTableView: UITableView!
@IBOutlet weak var myTableVieweHeight: NSLayoutConstraint!
//(call it whenever tableView is updated inside/outside delegate methods)
myTableView.updateHeight(myTableVieweHeigh)

Bonus : Can be used on any other UIViews eg:your own dynamic label

Solution 22 - Ios

If you want your table to be dynamic, you will need to use a solution based on the table contents as detailed above. If you simply want to display a smaller table, you can use a container view and embed a UITableViewController in it - the UITableView will be resized according to the container size.

This avoids a lot of calculations and calls to layout.

Solution 23 - Ios

Mu solution for this in swift 3: Call this method in viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMiMoView Question on Stackoverflow
Solution 1 - IosheyfrankView Answer on Stackoverflow
Solution 2 - IosMiMoView Answer on Stackoverflow
Solution 3 - IosMusa almatriView Answer on Stackoverflow
Solution 4 - IosAlexeyView Answer on Stackoverflow
Solution 5 - IosAnooj VMView Answer on Stackoverflow
Solution 6 - IosGulzView Answer on Stackoverflow
Solution 7 - IosxinatanilView Answer on Stackoverflow
Solution 8 - IosSreeraj VRView Answer on Stackoverflow
Solution 9 - IosYohan DahmaniView Answer on Stackoverflow
Solution 10 - IosĐorđe NilovićView Answer on Stackoverflow
Solution 11 - IosNikiView Answer on Stackoverflow
Solution 12 - IosAndréView Answer on Stackoverflow
Solution 13 - IosChrissiView Answer on Stackoverflow
Solution 14 - IosSandeepAggarwalView Answer on Stackoverflow
Solution 15 - IosDominic EggintonView Answer on Stackoverflow
Solution 16 - IosAalaaView Answer on Stackoverflow
Solution 17 - IosRamiroView Answer on Stackoverflow
Solution 18 - IosAnton TropashkoView Answer on Stackoverflow
Solution 19 - IosAshvinGudaliyaView Answer on Stackoverflow
Solution 20 - IosJelleView Answer on Stackoverflow
Solution 21 - IosAtkaView Answer on Stackoverflow
Solution 22 - Iosgreen_knightView Answer on Stackoverflow
Solution 23 - IosSiempayView Answer on Stackoverflow