Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When layout is complete for custom UITableViewCell?

I have a custom table view cell defined in the storyboard file.

enter image description here

This cell contains scroll view with few pages of content and a page indicator. Scroll views, content view and cell itself all defined using auto-layout. Page1/2/3 views are bind to have same width as scroll view, and container bind to have 3 times width as scroll view. This way I unsure that content is exactly sized to show three pages of information regardless of screen size.

I have a code in the custom cell class to update page indicator when scroll view did scroll - this works fine.

I also exposed the method in the cell class to specify page from outside, and in this setter I'm doing something like that:

- (void)setPage:(NSInteger)page animated:(BOOL)animated
{
    CGFloat pageWidth = self.scrollView.frame.size.width;
    self.pageControl.currentPage = page;
    [self.scrollView setContentOffset:CGPointMake(page*pageWidth, 0) animated:animated];
}

The problem I'm facing is quite often scroll view frame and content size is not defined properly when I'm calling this setter, so the page indicator would be set, but scroll view doesn't change content offset.

Where can I catch a moment that my cell auto-layout is complete and I can guarantee that setContentOffset will work? I have try to call setPage from cellForIndexPath - that obviously doesn't work, then I tried to call it from willDisplayCell - same story - cell itself is already laid out properly, but scroll view still has 600px (from storyboard) and it fails.

like image 375
sha Avatar asked Nov 20 '14 22:11

sha


3 Answers

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.

like image 72
sha Avatar answered Nov 06 '22 16:11

sha


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
    }
}
like image 26
Roman Aliyev Avatar answered Nov 06 '22 16:11

Roman Aliyev


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.

like image 43
A. R. Younce Avatar answered Nov 06 '22 16:11

A. R. Younce