When layout is complete for custom UITableViewCell?

I've found out that the overriding of the UITableViewCell's layoutSubviews is not working as it should. During the first call of the layoutSubviews I get incorrect bounds of the contentView. So I have found an alternative solution:

// implementation of the UITableViewDelegate protocol
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    DispatchQueue.main.async {
        // the layout of the cell is ready
    }
}

I ended up doing the following:

In the custom cell subclass added the layoutSubview implementation

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self.contentView layoutSubviews];

     // set proper content offset
    [self.scrollView setContentOffset:CGPointMake(self.frame.size.width*self.pageControl.currentPage, 0) animated:NO];
}

Without me directly calling layoutSubview on contentView scroll view didn't have proper frame even after [super layoutSubview] was finished.

I didn't have to store current page number in internal variable, since pageControl already holds it and it's set when configuring original cell information.

What was interesting though - originally I added [self.scrollView layoutSubview] inside that method and it was working fine in iOS8, but didn't do anything good in iOS7. I had to go up one level to contentView to make sure it works in both 7 and 8.


You seem to be set on a bespoke implementation, so here is what might work.

Calling your -setPage:animated: method prior to the table view setting up its subviews, giving the cell a chance to do the same, or auto layout kicking in won't work unless you maintain some state in the cell (probably just the page number) and react to one or more of the following opportunities:

  1. Waiting until the -viewDidLayoutSubviews method of your UITableViewController fires and then call -cellForRowAtIndexPath: to get the cell. The cell should then have a valid frame.

  2. Override -didMoveToSuperview in your custom UITableViewCell. By the time this is called the frame should also be valid.

After either of these points you could inspect your custom cell's state (which should contain the page number that is to be displayed) and scroll to the appropriate point. It would probably make sense to refactor your method to set the state (an integer property on the cell) and have a separate private method that -setPage:animated: and one of the two overrides above could call to calculate the offset and do the scrolling.


Suggested alternative:

You could pull this off by embedding a UICollectionView in the cell's contentView. Your UIViewController that is currently implementing UITableViewDataSource could also implement UICollectionViewDataSource and provide the collection view with its cells.

Assuming that your UITableView cells are the width and height of the screen you could configure the embedded collection view to basically paginate horizontally (since UICollectionView is a subclass of UIScrollView).

When setting up the collection view use UICollectionViewFlowLayout as the layout object and configure it up like so:

// Ensure the collection view scrolls horizontally.
let flowLayout = UICollectionViewFlowLayout();
flowLayout.scrollDirection = .Horizontal;

let collectionView = UICollectionView(frame:tableCellFrame, layout:flowLayout);

// These are actually UIScrollView properties.
collectionView.pagingEnabled = true;
collectionView.bounces = false;

You then place each page of content in a custom UICollectionViewCell and the collection view will paginate them for you. This has the added bonus of providing you with a paginated scrolling API for free.