How to vertical align (center) the content of UITableView?

Override viewDidLayoutSubviews and compare the tableView's frame with its contentSize to set its contentInset. In Swift 4 :

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let tableViewHeight = self.tableView.frame.height
    let contentHeight = self.tableView.contentSize.height

    let centeringInset = (tableViewHeight - contentHeight) / 2.0
    let topInset = max(centeringInset, 0.0)

    self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
}

This works well with self-sizing table view cells 👌


In the viewWillAppear and in the didRotateFromInterfaceOrientation functions :

CGFloat headerHeight = (self.view.frame.size.height - (ROW_HEIGHT * [self.tableView numberOfRowsInSection:0]))) / 2;

self.tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, -headerHeight, 0);

This will solve your problem.

EDIT

Call the updateTableViewContentInset function in the viewWillLayoutSubviews and after each reloadData :

Ojective-C

- (void)updateTableViewContentInset {
    CGFloat viewHeight = self.view.frame.size.height;
    CGFloat tableViewContentHeight = self.tableView.contentSize.height;
    CGFloat marginHeight = (viewHeight - tableViewContentHeight) / 2.0;

    self.tableView.contentInset = UIEdgeInsetsMake(marginHeight, 0, -marginHeight, 0);
}

Swift 4

func updateTableViewContentInset() {
    let viewHeight: CGFloat = view.frame.size.height
    let tableViewContentHeight: CGFloat = tableView.contentSize.height
    let marginHeight: CGFloat = (viewHeight - tableViewContentHeight) / 2.0

    self.tableView.contentInset = UIEdgeInsets(top: marginHeight, left: 0, bottom:  -marginHeight, right: 0)
}

Swift 3 based on Pipiks answer

let headerHeight: CGFloat = (view.frame.size.height - CGFloat(Int(tableView.rowHeight) * tableView.numberOfRows(inSection: 0))) / 2
tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, -headerHeight, 0)

Use the contentSize property of UITableView, that how you don't have to take the cell height or count into account.

- (void) updateTableInsetsForHeight: (CGFloat) height
{
    CGFloat tableViewInset = MAX((height - self.tableView.contentSize.height) / 2.0, 0.f);
    self.tableView.contentInset = UIEdgeInsetsMake(tableViewInset, 0, -tableViewInset, 0);
}

Update the contentInsets after reloading tableView-data, and when the containing view of the tableView becomes visible (viewWillAppear:) and when it changes size (viewWillTransitionToSize:withTransitionCoordinator:).

- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [self updateTableInsetsForHeight: size.height];
}