9

I would like to take advantage of the dynamic UITableViewCell height new in iOS 8. I need to place a UICollectionView within a UITableViewCell. I want to ensure all of the cells in the collection view are visible on screen, so the table cell should increase in height to fit the collection view. I almost have this working. I just haven't been able to get the table cell to be the right size - it's either too long or too short in height, and some layout issues are visible until I interact with the table (more on that below).

I've set up auto layout constraints for the collection view to the table's cell's contentView: leading, trailing, top, and bottom. I then created a height constraint on the collection view so I can update its constant on the fly after calculating the appropriate height to fit all cells in the collection view (because I don't think there's a way to do that automatically).

Here is the rest of the setup.

viewDidLoad {
    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 44
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    let cellDimension = self.collectionView.frame.size.width / 7 //always 7 columns

    let flowLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    flowLayout.itemSize = CGSizeMake(cellDimension, cellDimension)

    self.collectionViewHeightConstraint.constant = cellDimension * 4 //always 4 rows
}

I have not implemented heightForRowAtIndexPath.

This setup results in conflicting vertical constraints. I've tried reducing the priority of the constraints and changing the relation in a bunch of combinations, but none resulted in the desired behavior. If I reduce the priority of the height constraint to 999, the table view cell is too tall at first, but after I scroll up and down the table view the table cell height updates to the expected height. With some other combinations, the result is either the table cell is too short yet the full collection view bleeds out, the table cell is too short and the collection view is cut off, or the table cell is too tall, based on what priority and relations I've applied to the constraints. Additionally, the collection view cells are not displayed at the proper size while the view is animating in (but they're properly adjusted by the time animation finishes), or if I reload the table the collection view cells are the incorrect size until I scroll the table.

How can I resolve these appearance issues to obtain a dynamic table view cell height that contains a fully visible collection view featuring dynamic cell size based on the display's width?

Jordan H
  • 45,794
  • 29
  • 162
  • 306
  • I would suggest moving the rowHeight setting "self.tableView.rowHeight = " from ViewDidLoad to ViewWillAppear instead, You might want to check this question also. [link](http://stackoverflow.com/questions/29263875/uitableviewcells-initial-load-view-display-issue) – Kevin Horgan Apr 13 '15 at 09:02
  • @KevinHorgan That didn't make any noticeable difference. – Jordan H Apr 13 '15 at 15:01
  • In order to make autolayout work properly in UITableViewCell you need to add constraints for all layouts in containerView without missing.Otherwise autolayouts won't work properly.Check the following link which gives some detailed information of autolayout setup for tableviewcell http://stackoverflow.com/questions/29231390/uitableview-strange-layout-behavior-changes-on-scroll/29327082#29327082 – Ram Vadranam Apr 17 '15 at 17:01
  • @Ram The constraints are all defined, top bottom leading trailing from the collection view to the contentView. – Jordan H Apr 17 '15 at 23:12
  • AFAICS the answer is more complicated than anyone here has mentioned. Check out https://stackoverflow.com/questions/24126708/uicollectionview-inside-a-uitableviewcell-dynamic-height – xaphod Sep 15 '17 at 00:15

3 Answers3

1

From what i can understand from you code in setting up the collectionView cells and size, it seems to me that you want to have square collection cells, and have 4 rows of 7 cells and all fully visible.

If you add constraints to the collectionView that is in the cell to all 4 margins(top, bottom, left and right) and then add an Aspect Ratio Constraint of 7:4 to the collectionView, the tableview should be able to calculate cell height automatically for you at the right size.

pteofil
  • 4,047
  • 15
  • 27
  • This results in conflicting constraints. I also need the items to adjust to fill all available space - shouldn't be any spacing between each cell. – Jordan H Apr 25 '15 at 00:26
  • You have to set the tableView cell height accordingly in order to get rid of the conflicting constraints. When you run it the tableView will size the cell right, but in IB you have to do it. It shouldn't be that hard to calculate using the 7:4 ratio. To have the items fill all the available space you need to set the itemSize for the collectionView. But attention, you have one collectionView in each cell, so you have to set it in one of the functions of the tableView datasource. – pteofil Apr 27 '15 at 05:50
  • This got me thinking though, that what you are trying to achieve with collectionViews embedded in tableview cells, can be much easier accomplished with just ONE collectionView and what you were going to put in each tableView cell you put in sections in CollectionView. – pteofil Apr 27 '15 at 05:52
  • I don't think we're on the same page. I do only have a single `UICollectionView` inside of a single `UITableViewCell`, of course with other table view cells on screen as well. – Jordan H Apr 27 '15 at 06:08
  • You're right. I wrongly assumed that you have one collectionView in each cell. But my answer still apply to your problem. – pteofil Apr 27 '15 at 07:15
0

Here's how I'd approach this, and I think it can be done all in IB.

First, I'd set minimum sizes for the CV cells then I'd set the hugging priority of the CV to hug its contents as tightly as possible. That should guarantee that, all external influences apart, the CV will try to have the smallest possible size that makes all of its cells visible.

Next, I'd play the same game with the TBV cell's content view and the CV, that is, I'd set the hugging priority of the TBV cell's content view to hug the CV as tightly as possible. Again, this should enforce that, external influences neglected, the TBV cell should keep the smallest size it needs to keep in order to display the CV in full.

wltrup
  • 738
  • 1
  • 4
  • 13
0

Have a look at how forkingdog has solved the same problem of dynamic heights of tableview cells here > https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

You should be able to switch out the imageview for uicollectionview. enter image description here

In his tableview - rather than using the out of the box estimated row height - you'll need something like

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [tableView fd_heightForCellWithIdentifier:@"FDFeedCell" configuration:^(FDFeedCell *cell) {
        cell.entity = self.feedEntities[indexPath.row];
    }];
}



#import "UITableView+FDTemplateLayoutCell.h"
#import <objc/runtime.h>

@implementation UITableView (FDTemplateLayoutCell)

- (id)fd_templateCellForReuseIdentifier:(NSString *)identifier;
{
    NSAssert(identifier.length > 0, @"Expects a valid identifier - %@", identifier);

    NSMutableDictionary *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
    if (!templateCellsByIdentifiers) {
        templateCellsByIdentifiers = @{}.mutableCopy;
        objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
    if (!templateCell) {
        templateCell = [self dequeueReusableCellWithIdentifier:identifier];
        templateCellsByIdentifiers[identifier] = templateCell;
    }

    return templateCell;
}

- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration
{
    // Fetch a cached template cell for `identifier`.
    UITableViewCell *cell = [self fd_templateCellForReuseIdentifier:identifier];

    // Reset to initial height as first created, otherwise the cell's height wouldn't retract if it
    // had larger height before it gets reused.
    cell.contentView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.frame), self.rowHeight);

    // Manually calls to ensure consistent behavior with actual cells (that are displayed on screen).
    [cell prepareForReuse];

    // Customize and provide content for our template cell.
    if (configuration) {
        configuration(cell);
    }

    // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
    // of growing horizontally, in a flow-layout manner.
    NSLayoutConstraint *tempWidthConstraint =
    [NSLayoutConstraint constraintWithItem:cell.contentView
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:1.0
                                  constant:CGRectGetWidth(self.frame)];
    [cell.contentView addConstraint:tempWidthConstraint];

    // Auto layout does its math
    CGSize fittingSize = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    [cell.contentView removeConstraint:tempWidthConstraint];

    // Add 1px extra space for separator line if needed, simulating default UITableViewCell.
    if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
        fittingSize.height += 1.0 / [UIScreen mainScreen].scale;
    }

    return fittingSize.height;
}

@end
johndpope
  • 4,223
  • 2
  • 33
  • 37