ios UITableViewCell 内的 UICollectionView -- 动态高度?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24126708/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 00:43:46  来源:igfitidea点击:

UICollectionView inside a UITableViewCell -- dynamic height?

iosuitableviewuicollectionview

提问by Shadowman

One of our application screens requires us to place a UICollectionViewinside of a UITableViewCell. This UICollectionViewwill 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.

我们的一个应用程序屏幕要求我们UICollectionViewUITableViewCell. 这UICollectionView将具有动态数量的项目,从而导致也必须动态计算高度。但是,我在尝试计算嵌入的UICollectionView.

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

我们的总体UIViewController是在 Storyboards 中创建的,并且确实使用了自动布局。但是,我不知道如何UITableViewCell根据UICollectionView.

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

任何人都可以就如何实现这一目标提供一些提示或建议吗?

回答by Pablo Romeu

The right answer is YES, you CANdo this.

正确的答案是YES,你CAN做到这一点。

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

几周前我遇到了这个问题。它实际上比您想象的要容易。将您的单元格放入 NIB(或故事板)并固定它们以让自动布局完成所有工作

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 UIViewmethod that "does the magic":

解决方案是告诉自动布局首先计算 collectionViewCell 大小,然后是集合视图 contentSize,并将其用作单元格的大小。这是“神奇”的UIView方法:

-(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.

您必须在此处设置 TableViewCell 的大小,在您的情况下是 CollectionView 的 contentSize。

CollectionViewCell

集合视图单元格

At the CollectionViewCellyou 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).

CollectionViewCell每次更改模型时,您都必须告诉单元格进行布局(例如:您使用文本设置 UILabel,然后必须再次对单元格进行布局)。

- (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 TableViewCelldoes the magic. It has an outlet to your collectionView, enables the auto layout for collectionView cells using estimatedItemSizeof the UICollectionViewFlowLayout.

TableViewCell确实神奇。它有一个出口,以您的的CollectionView,能够使用的CollectionView细胞自动布局estimatedItemSize的的UICollectionViewFlowLayout

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

然后,诀窍是在 systemLayoutSizeFittingSize... 方法中设置 tableView 单元格的大小。(注:iOS8 或更高版本)

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 latefor the auto layout system to compute the CollectionView contentSize and sometimes you may find wrong resized cells.

注:我试图用的tableView的代表单元的高度方法-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath。但为时已晚的自动布局系统来计算的CollectionView contentSize,有时你可能会发现错误调整大小的细胞。

@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:

请记住在TableViewController 中为 tableView 单元启用自动布局系统:

- (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: @rbarberahelped to sort this out

信用:@rbarbera帮助解决了这个问题

回答by Igor

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

我认为我的解决方案比@PabloRomeu 提出的解决方案简单得多。

Step 1. Create outlet from UICollectionViewto UITableViewCell subclass, where UICollectionViewis placed. Let, it's name will be collectionView

步骤 1. 从UICollectionViewUITableViewCell subclass,在UICollectionView放置的位置创建出口。让,它的名字将是collectionView

Step 2. Add in IB for UICollectionViewheight constraint and create outlet to UITableViewCell subclasstoo. Let, it's name will be collectionViewHeight.

步骤 2. 添加UICollectionView高度约束的IB并创建出口UITableViewCell subclass。让,它的名字将是collectionViewHeight

Step 3. In tableView:cellForRowAtIndexPath:add code:

步骤 3. 在tableView:cellForRowAtIndexPath:添加代码中:

// deque a cell
cell.frame = tableView.bounds;
[cell layoutIfNeeded];
[cell.collectionView reloadData];
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;

回答by Rivera

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

表视图和集合视图都是UIScrollView子类,因此不喜欢嵌入到另一个滚动视图中,因为它们会尝试计算内容大小、重用单元格等。

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.

如果您的集合视图“部件”有一个基本的网格布局,您还可以使用常规表格单元格来处理它们。尽管如此,如果您不需要 iOS 5 支持,您应该更好地使用集合视图。

回答by jmad8

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 collectionViewin the systemLayoutSizeFittingfunction.

上面 Pablo Romeu 的回答(https://stackoverflow.com/a/33364092/2704206)对我的问题有很大帮助。但是,我必须做一些不同的事情才能解决我的问题。首先,我不必layoutIfNeeded()经常打电话。我只需要collectionViewsystemLayoutSizeFitting函数中调用它。

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.widthwhen setting the collectionView.frame's width. I also had to add the top and bottom margins to the return value CGSizeheight.

其次,我在表视图单元格中对我的集合视图进行了自动布局约束,以给它一些填充。所以我必须targetSize.width在设置collectionView.frame的宽度时减去前导和尾随边距。我还必须将顶部和底部边距添加到返回值CGSize高度。

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: { 
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
}
.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 UICollectionViewFlowLayoutas the layout for your collection view. Finally, make sure you link the collectionViewIBOutlet to your storyboard.

注意:您需要在情节提要中或创建它们的任何地方为这些约束设置标识符。除非他们有一个 0 常数,否则没关系。此外,在 Pablo 的回复中,您需要将其UICollectionViewFlowLayout用作集合视图的布局。最后,确保将collectionViewIBOutlet链接到故事板。

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 UICollectionViewDelegateFlowLayoutand UICollectionViewDataSourceprotocols. Hope this is helpful to someone else!

使用上面的自定义表格视图单元格,我现在可以在需要集合视图的任何其他表格视图单元格中对其进行子类化,并让它实现UICollectionViewDelegateFlowLayoutUICollectionViewDataSource协议。希望这对其他人有帮助!

回答by SRP-Achiever

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

我通读了所有的答案。这似乎适用于所有情况。

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()
}

回答by Dale

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

Pablo Romeu 的解决方案的替代方案是自定义 UICollectionView 本身,而不是在表格视图单元格中进行工作。

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

创建 UICollectionView 的子类并覆盖以下方法

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    dispatch_async(dispatch_get_main_queue()) {
        self.tableView.reloadData()
    }
}

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

(您还应该以与 reloadData() 类似的方式覆盖相关方法:reloadSections、reloadItemsAtIndexPaths)

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

调用 layoutIfNeeded 强制集合视图重新计算内容大小,然后可以将其用作新的内部大小。

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

此外,您需要在表视图控制器中显式处理视图大小的更改(例如在设备旋转时)

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

回答by Osa

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

到目前为止,我想出的最简单的方法是上面@igor 的回答,

In your tableviewcell class just insert this

在你的 tableviewcell 类中插入这个

 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
}

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

当然,在单元格的类中使用您的插座更改 collectionviewoutlet

回答by Nazmul Hasan

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

我从@Igor 的帖子中得到了想法,并迅速为我的项目投入了时间

Just past this in your

刚刚在你的

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {       
  //do dequeue stuff
  cell.layer.shouldRasterize = true
  cell.layer.rasterizationScale = UIScreen.main.scale
  return cell
}

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

另外:如果您在加载单元格时看到 UICollectionView 断断续续。

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
}

回答by Abd Al-rhman Taher Badary

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 @PabloRomeuapproach 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.

我最近遇到了同样的问题,我几乎尝试了答案中的所有解决方案,其中一些解决方案有效,而另一些解决方案则不是我对@PabloRomeu方法的主要关注是,如果您在单元格中有其他内容(集合视图除外)您将不得不计算它们的高度和它们的约束高度并返回结果以获得正确的自动布局,我不喜欢在我的代码中手动计算事物。所以这里的解决方案对我来说效果很好,而无需在我的代码中进行任何手动计算。

in the cellForRow:atIndexPathof the table view I do the following:

cellForRow:atIndexPath表视图中,我执行以下操作:

##代码##

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)

我认为这里发生的事情是我在计算集合视图高度后强制 tableview 单元格调整其高度。(将 collectionView 日期提供给数据源后)

回答by InkGolem

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 heightForRowAtIndexPathto return the proper size.

我会在集合视图类上放置一个静态方法,该方法将根据它将具有的内容返回一个大小。然后在 中使用该方法heightForRowAtIndexPath返回正确的大小。

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.

另请注意,当您嵌入这些类型的 viewController 时,您可能会遇到一些奇怪的行为。我做过一次,遇到了一些我从未解决过的奇怪记忆问题。