135

One of our application screens requires us to place a UICollectionView inside of a UITableViewCell. This UICollectionView will have a dynamic number of items, resulting in a height which must be calculated dynamically as well. However, I am running into problems trying to calculate the height of the embedded UICollectionView.

Our overarching UIViewController was created in Storyboards and does make use of auto layout. But, I don't know how to dynamically increase the height of the UITableViewCell based on the height of the UICollectionView.

Can anyone give some tips or advice on how to accomplish this?

Dharmesh Dhorajiya
  • 3,975
  • 9
  • 28
  • 39
Shadowman
  • 9,072
  • 17
  • 84
  • 170
  • can you tell a bit more about the layout you're trying to accomplish ? Is the height of the `UITableViewCell` different from its acutal tableview ? Do you need vertical scrolling on both the `UICollectionView` and the `UITableView` – apouche Jun 11 '14 at 20:10
  • 3
    The height of the UITableViewCell is supposed to be dynamic, based on the height of the UICollectionView that is part of the table view cell. The layout of my UICollectionView is 4 columns and a dynamic number of rows. So, with 100 items, I would have 4 columns and 25 rows in my UICollectionView. The height of the particular cell -- as indicated by UITableView's heightForRowAtIndexPath -- needs to be able to adjust based on the size of that UICollectionView. Is that a bit more clear? – Shadowman Jun 11 '14 at 20:40
  • @Shadowman , please suggest me a solution for this situation – Ravi Ojha Mar 01 '16 at 10:21

16 Answers16

133

The right answer is YES, you CAN do this.

I came across this problem some weeks ago. It is actually easier than you may think. Put your cells into NIBs (or storyboards) and pin them to let auto layout do all the work

Given the following structure:

TableView

TableViewCell

CollectionView

CollectionViewCell

CollectionViewCell

CollectionViewCell

[...variable number of cells or different cell sizes]

The solution is to tell auto layout to compute first the collectionViewCell sizes, then the collection view contentSize, and use it as the size of your cell. This is the UIView method that "does the magic":

-(void)systemLayoutSizeFittingSize:(CGSize)targetSize 
     withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority 
           verticalFittingPriority:(UILayoutPriority)verticalFittingPriority

You have to set here the size of the TableViewCell, which in your case is the CollectionView's contentSize.

CollectionViewCell

At the CollectionViewCell you have to tell the cell to layout each time you change the model (e.g.: you set a UILabel with a text, then the cell has to be layout again).

- (void)bindWithModel:(id)model {
    // Do whatever you may need to bind with your data and 
    // tell the collection view cell's contentView to resize
    [self.contentView setNeedsLayout];
}
// Other stuff here...

TableViewCell

The TableViewCell does the magic. It has an outlet to your collectionView, enables the auto layout for collectionView cells using estimatedItemSize of the UICollectionViewFlowLayout.

Then, the trick is to set your tableView cell's size at the systemLayoutSizeFittingSize... method. (NOTE: iOS8 or later)

NOTE: I tried to use the delegate cell's height method of the tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.but it's too late for the auto layout system to compute the CollectionView contentSize and sometimes you may find wrong resized cells.

@implementation TableCell

- (void)awakeFromNib {
    [super awakeFromNib];
    UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;

    // Configure the collectionView
    flow.minimumInteritemSpacing = ...;

    // This enables the magic of auto layout. 
    // Setting estimatedItemSize different to CGSizeZero 
    // on flow Layout enables auto layout for collectionView cells.
    // https://developer.apple.com/videos/play/wwdc2014-226/
    flow.estimatedItemSize = CGSizeMake(1, 1);

    // Disable the scroll on your collection view
    // to avoid running into multiple scroll issues.
    [self.collectionView setScrollEnabled:NO];
}

- (void)bindWithModel:(id)model {
    // Do your stuff here to configure the tableViewCell
    // Tell the cell to redraw its contentView        
    [self.contentView layoutIfNeeded];
}

// THIS IS THE MOST IMPORTANT METHOD
//
// This method tells the auto layout
// You cannot calculate the collectionView content size in any other place, 
// because you run into race condition issues.
// NOTE: Works for iOS 8 or later
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority {

    // With autolayout enabled on collection view's cells we need to force a collection view relayout with the shown size (width)
    self.collectionView.frame = CGRectMake(0, 0, targetSize.width, MAXFLOAT);
    [self.collectionView layoutIfNeeded];

    // If the cell's size has to be exactly the content 
    // Size of the collection View, just return the
    // collectionViewLayout's collectionViewContentSize.

    return [self.collectionView.collectionViewLayout collectionViewContentSize];
}

// Other stuff here...

@end

TableViewController

Remember to enable the auto layout system for the tableView cells at your TableViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Enable automatic row auto layout calculations        
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    // Set the estimatedRowHeight to a non-0 value to enable auto layout.
    self.tableView.estimatedRowHeight = 10;

}

CREDIT: @rbarbera helped to sort this out

Pablo Romeu
  • 2,033
  • 1
  • 13
  • 15
  • Question, how do you link the tableview and the collectionview in the storyboard? I assume you use a segue but i cannot choose which one. Also i'm coding in swift so i'm already struggling with the traduction of the code – thibaut noah Oct 30 '15 at 16:13
  • I create a NIB for the UITableViewCell, and a custom subclass for that cell (E.g. PRTableCell), then I put a UICollectionView -NOT a UICollectionViewController- inside the cell with auto layout constraints. Then I create an outlet for the collectionView into the PRTableCell. If you want to use storyboard, simply do the same with a cell Prototype and using a custom class. – Pablo Romeu Oct 31 '15 at 08:34
  • 5
    Yeah. This works. This should be the accepted answer. – csotiriou Dec 07 '15 at 13:08
  • I can't get this to work in Swift. I have exactly the same problem, I used https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/ as a guide. But having a "fixed" dynamic height doesnt seem to work for me. – hugocarlmartin Feb 08 '16 at 15:25
  • 1
    Can any body provide me a sample code for it? i want to show multiple images in vertical collection view , which is inside of tableview cell.. – Ravi Ojha Feb 16 '16 at 13:20
  • @PabloRomeu can you please explain it in sample , i am new in ios – Ravi Ojha Mar 01 '16 at 09:59
  • @PabloRomeu Great right up, this really helped me. Thank you – MobileVet May 14 '16 at 00:35
  • @PabloRomeu, My long search ends here. Thanks – pkc456 Jun 06 '16 at 11:10
  • 4
    Finally this is worked for me, but I had to do an other trick: `cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), 10000);` This will "simulate" a long cell with a long collectionView, so the collectionView will compute the size all of its cells. Otherwise the collectionView computed only the visible cell sizes, and set wrongly the talbeViewCell height. – ingaham Aug 08 '16 at 12:08
  • 1
    If you have a collectionView with elements located outside of the collectionView, it's real size won't be computed, the estimatedSize will be used instead of this. The real size will be computed only if these cell will be visible, bat that's too late, because at this point your tableViewCell has a wrong height setup already. So no, the `estimatedSize` won't do the trick – ingaham Aug 08 '16 at 13:35
  • Question (before I go and try this solution): In my case, the collection view is PART of the cell. The cell has other components and currently Autolayout is calculating the cell height for me, since the other components are dynamic. So far the collection view has a fixed height, but I want to make it dynamic too. Will this work? How? My idea is to use this method to change a height constraint for the collection view, not for the cell.... is that the right path? – gonso Aug 23 '16 at 09:41
  • 1
    @gonso I made it work using a height constraint on the collection view in the systemLayoutSizeFittingSize method. `collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize().height` then `return self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)` – Paludis Aug 25 '16 at 06:52
  • @ingaham Exactly, the `cell.bounds` trick is the most important part. I am using a tableView inside a tableView cell (almost the same setup) and without setting the outer table's `cell.bounds` to some huge height it doesn't compute the `contentSize` correctly. – gigo Nov 01 '16 at 08:56
  • I'm trying this way but the only thing I've got is the app to freeze :( @ingaham Where did you place that cells.bouds statement? – WedgeSparda Apr 20 '17 at 11:12
  • @WedgeSparda: When I'm configuring the cell in the `tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)`. I'm setting at first the `cell.bounds`, then the full cell layout will be configured – ingaham Apr 20 '17 at 14:34
  • @ingaham Tried that, but have the same problem. If I scroll quickly the height of the cell containing the collectionView if not correct. After a reloadData on the tableView the cell gets the correct height. Is really weird. – WedgeSparda Apr 21 '17 at 11:26
  • @PabloRomeu, Hello bro, Could you help me to look at my question https://stackoverflow.com/questions/44541403/collection-view-inside-table-does-not-load-immediately-in-swift-3?noredirect=1#comment76078645_44541403 bro? – May Phyu Jun 20 '17 at 05:03
  • @PabloRomeu by this methos the collection has some extra spaces. how to get rid of it. When we have to bindWithModel mentioned in your code – Ekra Jun 25 '17 at 21:39
  • bindWithModel means you have to set your model to your cell there. That's important because maybe you have to setup -for example- a UILabel that may change the cell's size. – Pablo Romeu Jun 26 '17 at 10:08
  • 21
    when setting the frame height to maximum in `systemLayoutSizeFittingSize`, i hit a problem in swift when reloading the cell, apps seems to stuck after the `self.collectionView.layoutIfNeeded()`. I fixed it by setting the frame height with 1 rather than maximum height. Hope its helps if anyone stumble in this problem. – titan Aug 16 '17 at 17:01
  • What if my CollectionView's flow is horizontal? In that case, the collectionViewCell can grow vertically? – nr5 Sep 07 '17 at 05:44
  • @Nil It will. My collection view had no flow and was multiline, I grew up the tableViewCell to the full size of the collection so it can fit fully inside. – Pablo Romeu Sep 08 '17 at 06:33
  • 1
    @PabloRomeu I'm having same issue as @WedgeSparda, and I can see why: in `systemLayoutSizeFittingSize` right before calling `layoutIfNeeded` on the collectionview, I can see that `self.bounds` (the bounds of the tableview cell) has a width of 363 pixels. But after calling `layoutIfNeeded` on the collectionview, the size of the collectionview (contentSize, or frame) has a larger width (606 pixels), even though it is pinned with constraints to the inside of the tableview cell. Thus the first time `systemLayoutSizeFittingSize` returns a value, the value is wrong. – xaphod Sep 14 '17 at 21:11
  • @xaphod that's because the table cell is still not still at full size... – Pablo Romeu Sep 15 '17 at 07:36
  • 6
    Here's what fixed it for me - in `systemLayoutSizeFitting`, reverse the order of what's shown in the answer currently, so that first `collectionView.layoutIfNeeded()` runs, then `collectionView.frame = CGRect.init(origin: .zero, size: CGSize.init(width: targetSize.width, height: 1))` – xaphod Sep 15 '17 at 13:55
  • I have a UIView inside the TableViewCell and inside that view, I place the collectionView. I create a subclass of this view. This subclass of UIView is the datasource and delegate for collectionView and am putting in the systemLayoutSizeFitting method inside that view subclass however, it is not working. I have the outlet of UIView subclass inside my TableViewCell and outlet for the collectionView inside the UIView subclass. – letsbondiway Dec 28 '17 at 21:41
  • Does anybody know how to make it work in Swift with Autolayout? Are there any examples? Thank you! – Alexey Chekanov Feb 20 '18 at 00:42
  • 1
    @AlexeyChekanov here is my code, I'm setting constraints with AutoLayout on my Storyboard and using their outlets in my code. My cell has a label and under it a collection view with varying number of buttons in different sizes. So I'm letting Autolayout set the position of things and then taking all heights together for the cell height size. Please see: https://gist.github.com/boazFrenkel/35b875f5d026e96981225dfcc9ad0623 – Boaz Frenkel Jul 11 '18 at 12:43
  • This is a great answer! Also @titan's comment is very important – Andrey Chernukha Feb 05 '19 at 15:41
  • 1
    Just noticed. Sometimes it doesn't work. Doesn't size the table view cell to take the whole collection view height – Andrey Chernukha Feb 06 '19 at 16:00
  • 1
    Not working for horizontal scroll. TableView Cell is not resizing. – Darshan Mothreja Jul 23 '20 at 10:54
  • I have copied this solution exactly except I am using a horizontal scroll on the CollectionView. I cant get it to work. Any ideas? – Nate4436271 Aug 27 '20 at 22:37
  • This worked for me. Not sure why. override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { collectionView.layoutIfNeeded() collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: 1) collectionView.layoutIfNeeded() return collectionView.contentSize } – garg Oct 25 '20 at 17:33
  • This Worked for me. Call super.systemLayoutSizeFitting inside tableview systemLayoutSizeFitting method. in addition no need to assign collection view.frame – Bhavesh.iosDev Mar 04 '21 at 09:45
  • I've only overwrite systemLayoutSizeFittingSize to make it work and didn't use estimatedSize only itemSize, thank you. Also, why did you setNeedsLayout in the collection cell ? You already layoutIfNeeded in the tableviewcell systemLayoutSizeFittingSize and the bindWithModel @PabloRomeu – OhadM May 18 '21 at 13:54
109

I think my solution is much simpler than the one proposed by @PabloRomeu.

Step 1. Create outlet from UICollectionView to UITableViewCell subclass, where UICollectionView is placed. Let, it's name will be collectionView

Step 2. Add in IB for UICollectionView height constraint and create outlet to UITableViewCell subclass too. Let, it's name will be collectionViewHeight.

Step 3. In tableView:cellForRowAtIndexPath: add code:

// deque a cell
cell.frame = tableView.bounds;
[cell layoutIfNeeded];
[cell.collectionView reloadData];
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;
Trev14
  • 2,516
  • 2
  • 22
  • 31
Igor
  • 11,227
  • 4
  • 49
  • 69
  • 1
    Have you actually tried this? It doesn't seem possible to both set a height constraint on the collection view and pin it to the margins of the table view cell. You either end up with too few constraints (hence a zero size warning), or you end up with conflicting constraints (one of which is ignored). Please explain how to set the constraints for this solution. – Dale Jul 19 '16 at 01:24
  • 2
    I have it worked. I add constraints for `collectionView` to all sides of `UITableViewCell` and set `tableView.estimatedRowHeight = 44;` and `tableView.rowHeight = UITableViewAutomaticDimension;` – Igor Jul 19 '16 at 01:29
  • 3
    This one works like champ...simple and efficient...thank you. If for somebody it is not working then probably the reason might be, we need to bind the bottom of collection view to bottom of table view cell. Then it will start working. Happy coding..:) – Sebin Roy Oct 13 '16 at 11:36
  • 2
    Would this work when the screen is resized, say by rotating an iPad? That would potentially change the height of the collection view, and therefore the height of the cell, but if on-screen the cell may not necessarily be reloaded by the table view, thus never calling the cellForRowAtIndexPath: method and not giving you a chance to change the constraint constant. It may be possible to force a reloadData though. – Carl Lindberg Oct 29 '16 at 15:22
  • I think, yes, it's needed to force reloadData on change Interface orientation. – Igor Oct 29 '16 at 15:31
  • 2
    Have you tried different collectionView cell's sizes? That was my problem. If you have different collectionView's cell sizes it did not work... :( – Pablo Romeu Nov 02 '16 at 15:53
  • Does someone have this working in a sample project they could share? – Clifton Labrum Nov 18 '16 at 17:53
  • @PabloRomeu, I am facing same problem. Any solution? – pkc456 Feb 13 '17 at 11:48
  • @Igor I'm running into a similar issue..can you assist me with my question? https://stackoverflow.com/questions/45176747/how-to-dynamically-change-the-height-of-a-uitableview-cell-containing-a-uicollec – Pangu Jul 19 '17 at 00:27
  • There's a different EASIER approch for this, just implment the last line of your code in the table cell's layoutSubviews() class – Osa Jan 08 '18 at 00:51
  • 1
    I needed to adapt your solution a bit, but it worked in the end! What I did was reverse the order of these two lines: `[cell layoutIfNeeded]; [cell.collectionView reloadData];` – Bogdan Razvan Feb 07 '18 at 13:19
  • this will work But my problem is that inside the collection view I have another collection view and the main collection view has just two page (paging mode) and each page has collection view how can I handle that ? – Saeed Rahmatolahi Apr 17 '18 at 04:22
  • @Igor, Do you have any sample code for this? Because I have tried and it won't work for me... It would be great if you can provide any sample code for it. – mrunal thanki Jun 18 '18 at 15:18
  • The collectionView reload is taking time and if i change my font size of labels inside the collectionview cell and reload, the latest contentsize is not reflected. – Rakesha Shastri Jan 28 '19 at 07:30
  • 1
    What I don't understand is what this line cell.frame = tableView.bounds; for. Can anybody explain this please? – Andrey Chernukha Feb 05 '19 at 15:21
  • I was using a collectionView inside a collectionViewCell and I couldn't achieve the dynamic height behaviour I wanted. I changed the encapsulating collectionView with a tableView, and now I had a structure like the one described by OP. This method was the only one that worked for me! Thanks @Igor – Bogdan Razvan Apr 25 '19 at 12:01
  • This is useful for me like one table view than use CollectionViewCell and tableview inside my tableview. nice work . – Chandan Jee Jun 28 '20 at 21:14
22

Both table views and collection views are UIScrollView subclasses and thus don't like to be embedded inside another scroll view as they try to calculate content sizes, reuse cells, etc.

I recommend you to use only a collection view for all your purposes.

You can divide it in sections and "treat" some sections' layout as a table view and others as a collection view. After all there's nothing you can't achieve with a collection view that you can with a table view.

If you have a basic grid layout for your collection view "parts" you can also use regular table cells to handle them. Still if you don't need iOS 5 support you should better use collection views.

Rivera
  • 10,182
  • 3
  • 49
  • 96
  • 1
    You've put "treat" in quotes, because you can NOT define different layouts per CollectionView section, right? I haven't found an option to define different layouts for different collection view sections. Am I overlooking something or are you talking of responding differently to 'layoutAttributesForItemAtIndexPath' for different sections in my own custom subclass of UICollectionViewLayout? – alex da franca May 04 '15 at 13:02
  • Yes -> Are you talking of responding differently to `layoutAttributesForItemAtIndexPath` for different sections in my own custom subclass of `UICollectionViewLayout`? – Rivera May 04 '15 at 14:11
  • 2
    @Rivera, your answer is most reasonable and sounds just correct. I have read this suggestion on many places. Could you also share some link to sample or tute on how to accomplish that or at least some pointers. TIA – thesummersign Jun 22 '15 at 06:21
  • 3
    Your answer assumes you are starting from scratch, and don't already have a big complex tableview that you need to fit into. The question specifically asked about putting a collectionview in a tableview. IMO your response is not an "answer" – xaphod Sep 14 '17 at 21:04
  • 2
    This is a bad answer. Having collection views inside table views is such a common thing to do, almost every app does it. – crashoverride777 Jun 08 '18 at 15:36
11

Pablo Romeu's answer above (https://stackoverflow.com/a/33364092/2704206) helped me immensely with my issue. I had to do a few things differently, however, to get this working for my problem. First off, I didn't have to call layoutIfNeeded() as often. I only had to call it on the collectionView in the systemLayoutSizeFitting function.

Secondly, I had auto layout constraints on my collection view in the table view cell to give it some padding. So I had to subtract the leading and trailing margins from the targetSize.width when setting the collectionView.frame's width. I also had to add the top and bottom margins to the return value CGSize height.

To get these constraint constants, I had the option of either creating outlets to the constraints, hard-coding their constants, or looking them up by an identifier. I decided to go with the third option to make my custom table view cell class easily reusable. In the end, this was everything I needed to get it working:

class CollectionTableViewCell: UITableViewCell {

    // MARK: -
    // MARK: Properties
    @IBOutlet weak var collectionView: UICollectionView! {
        didSet {
            collectionViewLayout?.estimatedItemSize = CGSize(width: 1, height: 1)
            selectionStyle = .none
        }
    }

    var collectionViewLayout: UICollectionViewFlowLayout? {
        return collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    }

    // MARK: -
    // MARK: UIView functions
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        collectionView.layoutIfNeeded()

        let topConstraintConstant = contentView.constraint(byIdentifier: "topAnchor")?.constant ?? 0
        let bottomConstraintConstant = contentView.constraint(byIdentifier: "bottomAnchor")?.constant ?? 0
        let trailingConstraintConstant = contentView.constraint(byIdentifier: "trailingAnchor")?.constant ?? 0
        let leadingConstraintConstant = contentView.constraint(byIdentifier: "leadingAnchor")?.constant ?? 0

        collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width - trailingConstraintConstant - leadingConstraintConstant, height: 1)

        let size = collectionView.collectionViewLayout.collectionViewContentSize
        let newSize = CGSize(width: size.width, height: size.height + topConstraintConstant + bottomConstraintConstant)
        return newSize
    }
}

As a helper function to retrieve a constraint by identifier, I add the following extension:

extension UIView {
    func constraint(byIdentifier identifier: String) -> NSLayoutConstraint? {
        return constraints.first(where: { $0.identifier == identifier })
    }
}

NOTE: You will need to set the identifier on these constraints in your storyboard, or wherever they are being created. Unless they have a 0 constant, then it doesn't matter. Also, as in Pablo's response, you will need to use UICollectionViewFlowLayout as the layout for your collection view. Finally, make sure you link the collectionView IBOutlet to your storyboard.

With the custom table view cell above, I can now subclass it in any other table view cell that needs a collection view and have it implement the UICollectionViewDelegateFlowLayout and UICollectionViewDataSource protocols. Hope this is helpful to someone else!

jmad8
  • 481
  • 5
  • 5
8

I read through all the answers. This seems to serve all cases.

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        collectionView.layoutIfNeeded()
        collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width , height: 1)
        return collectionView.collectionViewLayout.collectionViewContentSize
}
SRP-Achiever
  • 385
  • 3
  • 8
3

An alternative to Pablo Romeu's solution is to customise UICollectionView itself, rather than doing the work in table view cell.

The underlying problem is that by default a collection view has no intrinsic size and so cannot inform auto layout of the dimensions to use. You can remedy that by creating a custom subclass which does return a useful intrinsic size.

Create a subclass of UICollectionView and override the following methods

override func intrinsicContentSize() -> CGSize {
    self.layoutIfNeeded()

    var size = super.contentSize
    if size.width == 0 || size.height == 0 {
        // return a default size
        size = CGSize(width: 600, height:44)
    }

    return size
 }

override func reloadData() {
    super.reloadData()
    self.layoutIfNeeded()
    self.invalidateIntrinsicContentSize()
}

(You should also override the related methods: reloadSections, reloadItemsAtIndexPaths in a similar way to reloadData())

Calling layoutIfNeeded forces the collection view to recalculate the content size which can then be used as the new intrinsic size.

Also, you need to explicitly handle changes to the view size (e.g. on device rotation) in the table view controller

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    dispatch_async(dispatch_get_main_queue()) {
        self.tableView.reloadData()
    }
}
Dale
  • 2,855
  • 21
  • 25
  • bro could you please help my problem https://stackoverflow.com/questions/44650058/how-to-display-dynamically-data-from-server-in-collectionviewcell-in-tableviewce?noredirect=1#comment76287331_44650058 ? – May Phyu Jun 20 '17 at 11:26
3

Easiest approach I've came up with, so far, Credits to @igor answer above,

In your tableviewcell class just insert this

override func layoutSubviews() {
    self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height
}

and of course, change the collectionviewoutlet with your outlet in the cell's class

Osa
  • 1,712
  • 7
  • 27
  • 48
3

I was facing the same issue recently and I almost tried every solution in the answers, some of them worked and others didn't my main concern about @PabloRomeu approach is that if you have other contents in the cell (other than the collection view) you will have to calculate their heights and the heights of their constraints and return the result to get the auto layout right and I don't like to calculate things manually in my code. So here is the solution that worked fine for me without doing any manual calculations in my code.

in the cellForRow:atIndexPath of the table view I do the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //do dequeue stuff
    //initialize the the collection view data source with the data
    cell.frame = CGRect.zero
    cell.layoutIfNeeded()
    return cell
}

I think what happens here is that I force the tableview cell to adjust its height after the collection view height has been calculated. (after providing the collectionView date to the data source)

Mr.Kushwaha
  • 482
  • 6
  • 13
2

I would put a static method on the collection view class that will return a size based on the content it will have. Then use that method in the heightForRowAtIndexPath to return the proper size.

Also note that you can get some weird behavior when you embed these kinds of viewControllers. I did it once and had some weird memory issues I never worked out.

InkGolem
  • 2,502
  • 1
  • 12
  • 21
  • For me, I was trying to embed a table view controller inside collection view controllers. Every time a cell is re-used, it ended up remembering some autolayout constraints I applied onto them. It was very fiddly and ultimately was a terrible idea. I think I will just use a single collection view instead. – fatuhoku Mar 30 '15 at 21:44
2

Maybe my variant will be useful; i've been deciding this task during last two hours. I don't pretend it's 100% correct or optimal, but my skill's very small yet and i'd like to hear comments from experts. Thank you. One important note: this works for static table - it's specified by my current work. So, all I use is viewWillLayoutSubviews of tableView. And a little bit more.

private var iconsCellHeight: CGFloat = 500 

func updateTable(table: UITableView, withDuration duration: NSTimeInterval) {
    UIView.animateWithDuration(duration, animations: { () -> Void in
        table.beginUpdates()
        table.endUpdates()
    })
}

override func viewWillLayoutSubviews() {
    if let iconsCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 1)) as? CategoryCardIconsCell {
        let collectionViewContentHeight = iconsCell.iconsCollectionView.contentSize.height
        if collectionViewContentHeight + 17 != iconsCellHeight {
            iconsCellHeight = collectionViewContentHeight + 17
            updateTable(tableView, withDuration: 0.2)
        }
    }
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    switch (indexPath.section, indexPath.row) {
        case ...
        case (1,0):
            return iconsCellHeight
        default:
            return tableView.rowHeight
    }
}
  1. I know, that the collectionView is located in the first row of the second section;
  2. Let the height of the row is 17 p. bigger, than its content height;
  3. iconsCellHeight is a random number as the program starts (i know, that in the portrait form it has to be exactly 392, but it's not important). If the content of collectionView + 17 is not equal this number, so change its value. Next time in this situation the condition gives FALSE;
  4. After all update the tableView. In my case its the combination of two operations (for nice updating of extending rows);
  5. And of course, in the heightForRowAtIndexPath add one row to code.
1

I get idea from @Igor post and invest my time to this for my project with swift

Just past this in your

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //do dequeue stuff
    cell.frame = tableView.bounds
    cell.layoutIfNeeded()
    cell.collectionView.reloadData()
    cell.collectionView.heightAnchor.constraint(equalToConstant: cell.collectionView.collectionViewLayout.collectionViewContentSize.height)
    cell.layoutIfNeeded()
    return cell
}

Addition: If you see your UICollectionView choppy when loading cells.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {       
  //do dequeue stuff
  cell.layer.shouldRasterize = true
  cell.layer.rasterizationScale = UIScreen.main.scale
  return cell
}
Nazmul Hasan
  • 8,494
  • 5
  • 44
  • 64
0

Pablo's solution did not work very well for me, I had strange visual effects (the collectionView not adjusting correctly).

What worked was to adjust the height constraint of the collectionView (as a NSLayoutConstraint) to the collectionView contentSize during layoutSubviews(). This is the method called when autolayout is applied to the cell.

// Constraint on the collectionView height in the storyboard. Priority set to 999.
@IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!


// Method called by autolayout to layout the subviews (including the collectionView).
// This is triggered with 'layoutIfNeeded()', or by the viewController
// (happens between 'viewWillLayoutSubviews()' and 'viewDidLayoutSubviews()'.
override func layoutSubviews() {

    collectionViewHeightConstraint.constant = collectionView.contentSize.height
    super.layoutSubviews()
}

// Call `layoutIfNeeded()` when you update your UI from the model to trigger 'layoutSubviews()'
private func updateUI() {
    layoutIfNeeded()
}
Frédéric Adda
  • 5,240
  • 4
  • 50
  • 67
0
func configure(data: [Strings]) {
        
        names = data
        contentView.layoutIfNeeded()
        collectionviewNames.reloadData()
    }

Short and sweet. Consider the above method in your tableViewCell class. You would probably call it from func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell after dequeing your cell. Before calling reloadData on your collection view, in your tableCell, you need to tell the collection view to lay out its subviews, if layout updates are pending.

nr5
  • 3,803
  • 7
  • 38
  • 68
-3

In your UITableViewDelegate:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return ceil(itemCount/4.0f)*collectionViewCellHeight;
}

Substitute itemCount and CollectionViewCellHeight with the real values. If you have an array of arrays itemCount might be:

self.items[indexPath.row].count

Or whatever.

Martin
  • 1,105
  • 1
  • 8
  • 19
-3

1.Create dummy cell.
2.Use collectionViewContentSize method on UICollectionViewLayout of UICollectionView using current data.

Parag Shinde
  • 176
  • 11
-5

You can calculate the height of the collection based on its properties like itemSize, sectionInset, minimumLineSpacing, minimumInteritemSpacing, if your collectionViewCell has the border of a rule.

Enea Dume
  • 2,571
  • 3
  • 14
  • 33
sablib
  • 23
  • 3