When does a UITableView's contentSize get set?

I have a non scrolling UITableView in a UIScrollView. I set the frame size of the UITableView to its content size.

When I add a row to the UITableView, I call insertRowsAtIndexPaths:withRowAnimation: on the UITableView. Then I call a method to resize the frame of the UITableView:

  • How to get call history in iphone?
  • Accessing Core Data SQL Database in iOS 8 Extension (Sharing Data Between App and Widget Extension)
  • iOS: Removing UINavigationBar animation
  • add Shadow on UIView using swift 3
  • Extending CLPlacemark results in EXC BAD ACCESS
  • ios uitableview fade bottom cell and top cell as you scroll
  • - (void)resizeTableViewFrameHeight
    {
        // Table view does not scroll, so its frame height should be equal to its contentSize height
        CGRect frame = self.tableView.frame;
        frame.size = self.tableView.contentSize;
        self.tableView.frame = frame;
    }
    

    It seems though that the contentSize hasn’t been updated at this point. If I manually calculate the frame in the above method based on the number of rows and sections, then the method works properly.

    My question is, how can I get the UITableView to update its contentSize? I suppose I could call reloadData and that would probably do it, but it seems inefficient to reload the entire table when I’m just inserting one cell.

    6 Solutions Collect From Internet About “When does a UITableView's contentSize get set?”

    You can force the UITableView to calculate the size of the content by calling layoutIfNeeded on the UITableView.

    Example for a UITableViewController subclass that should be in a container view with variable size:

    - (CGSize)preferredContentSize
    {
        // Force the table view to calculate its height
        [self.tableView layoutIfNeeded];
        return self.tableView.contentSize;
    }
    

    Update 2016-07-28
    This is an untested Swift example of the same thing. It should work but if it doesn’t please leave a comment. There might be nicer or more idiomatic ways to do this. Unfortunately I am not very familiar with Swift.

    override var preferredContentSize: CGSize {
        get {
            self.tableView.layoutIfNeeded()
            return self.tableView.contentSize
        }
        set {}
    }
    

    While I don’t love KVO (Key-Value Observing), it’s a fairly good way to know exactly when your table’s contentSize has changed (rather than just calling your update methods in a bunch of random places). It’s rather simple to use (though somewhat cryptic and implicit). To observe changes on your table’s contentSize, do the following:

    1) Become an observer of your table’s contentSize property like so:

    [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:NULL];
    

    This is usually done in the view controller that holds the tableView (like in viewDidLoad:, for example).

    2) Implement the KVO observing method and make the changes you need:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        if(object == self.tableView && [keyPath isEqualToString:@"contentSize"]) {
            // perform your updates here
        }
    }
    

    3) Remove your view controller as an observer at some logical point (I call it in dealloc). You do this like so:

    - (void)dealloc {
        [self.tableView removeObserver:self forKeyPath:@"contentSize"];
    }
    

    Try this:

    - (void)resizeTableViewFrameHeight
    {
        UITableView *tableView = self.tableView;
        CGRect frame = tableView.frame;
        frame.size.height = [tableView sizeThatFits:CGSizeMake(frame.size.width, HUGE_VALF)].height;
        tableView.frame = frame;
    }
    
    1. Add observer (in my sample in viewDidLoad

      tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
      
    2. Observe value

      override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
          if let obj = object as? UITableView {
              if obj == self.tableView && keyPath == "contentSize" {
                  if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                      //do stuff here
                  }
              }
          }
      }
      
    3. Remove observer when not needed

      deinit {
          self.tableView.removeObserver(self, forKeyPath: "contentSize")
      }
      

    You can change the frame of footer. I call before animated appear of footer.

    if self.newTableView.contentSize.height < self.newTableView.frame.height {
            let additionalHeight: CGFloat = self.newTableView.frame.height - self.newTableView.contentSize.height
    
            let tableFooterView = self.newTableView.tableFooterView!
    
            let x = tableFooterView.frame.origin.x
            let y = tableFooterView.frame.origin.y + additionalHeight
            let width = tableFooterView.frame.width
            let height = tableFooterView.frame.height
    
            tableFooterView.frame = CGRect(x: x, y: y, width: width, height: height)
        }
    

    This worked for me, when addressing the issues of the tableview getting the right size after adding / removing rows

    Using

        tableView.layoutIfNeeded()
    

    and setting

        tableView.estimatedRowHeight = UITableViewAutomaticDimension