UITableViewCell 中的 UICollectionView ——动态高度? ?

我们的一个应用程序屏幕要求我们在 UITableViewCell中放置一个 UICollectionView。这个 UICollectionView将有一个动态数量的项目,导致一个高度,必须动态计算以及。但是,我在计算嵌入式 UICollectionView的高度时遇到了问题。

我们的总体 UIViewController是在故事板中创建的,确实使用了自动布局。但是,我不知道如何根据 UICollectionView的高度动态增加 UITableViewCell的高度。

有人能给出一些如何实现这一目标的提示或建议吗?

70355 次浏览

我将在集合视图类上放置一个静态方法,该方法将根据集合视图类所包含的内容返回一个大小。然后在 heightForRowAtIndexPath中使用该方法返回适当的大小。

还要注意,在嵌入这些 viewController 时,可能会出现一些奇怪的行为。我做过一次,有些奇怪的记忆问题,我从来没有解决过。

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

我建议您在所有情况下只使用集合视图。

您可以将其划分为若干部分,并将某些部分的布局“视为”表视图,而将其他部分“视为”集合视图。毕竟,使用集合视图可以实现表视图所能实现的任何目标。

如果您的集合视图“部件”具有基本的网格布局,那么还可以使用常规表单元格来处理它们。不过,如果你不需要 iOS5的支持,你应该更好地使用集合视图。

在你的 UITableView 代表:

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

用实际值替换 itemCount 和 CollectionViewCellHeight。如果有一个数组数组 itemCount 可能是:

self.items[indexPath.row].count

或者别的什么。

1. 创建虚拟单元。
2. 使用当前数据对 UICollectionView 的 UICollectionViewLayout 使用 CollectionViewContentSize 方法。

如果您的 collectionViewCell具有规则的边框,则可以根据它的属性(如 itemSizesectionInsetminimumLineSpacingminimumInteritemSpacing)计算集合的高度。

正确的答案是 是的,你 可以这样做。

几周前我遇到了这个问题。实际上比你想象的要简单。将您的单元格放入 NIBs (或故事板)中,并将它们钉住,让自动布局完成所有工作

鉴于以下结构:

TableView

TableViewCell

CollectionView

CollectionViewCell

CollectionViewCell

CollectionViewCell

[ ... 可变的细胞数量或不同的细胞大小]

解决方案是告诉自动布局首先计算 CollectionViewCell 大小,然后是集合视图 contentSize,并使用它作为单元格的大小。这是 UIView方法,“做的魔术”:

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

您必须在这里设置 TableViewCell 的大小,在您的示例中是 CollectionView 的 contentSize。

CollectionViewCell

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

TableViewCell的确很神奇。它有一个到 CollectionView 的出口,使用 UICollectionViewFlowLayout估计项目大小为 CollectionView 单元格启用自动布局。

然后,技巧是 将 tableView 单元格的大小设置为 systemLayoutSizeFittingSize... 方法(注意: iOS8或更高版本)

注意: 我尝试使用 tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.but 太迟了的委托单元格的 height 方法来计算 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

请记住在 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;


}

图片来源: @ rbarbera帮助解决了这个问题

也许我的变体会有用; 我在过去的两个小时里一直在决定这个任务。我不会假装它是100% 正确或最佳的,但我的技能还很小,我想听听专家的意见。谢谢你。 一个重要的注意事项: 这适用于静态表-它是由我当前的工作指定的。 所以,我使用的是 TableView视图,再多一点。

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. 我知道,CollectionView 位于第二部分的第一行;
  2. 让行的高度比其内容高度大17便士;
  3. 在程序启动时 CellHeight 是一个随机数(我知道,在肖像格式中它必须是精确的392,但这并不重要)。如果 CollectionView + 17的内容不等于此数字,则更改其值。下次在这种情况下,条件给予假;
  4. 毕竟更新了 tableView。在我的例子中,它是两个操作的组合(用于更新扩展行) ;
  5. 当然,在 heightForRowAtIndexPath 中向代码中添加一行。

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

步骤1。创建从 UICollectionViewUITableViewCell subclass的插座,在这里放置 UICollectionView。让,它的名字将是 collectionView

第二步。为 UICollectionView高度限制添加 IB,并为 UITableViewCell subclass创建出口。让,它的名字将是 collectionViewHeight

tableView:cellForRowAtIndexPath:中添加代码:

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

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

根本的问题是,默认情况下集合视图没有内部大小,因此不能通知要使用的维度的自动布局。您可以通过创建一个自定义子类来补救这个问题,该子类返回一个有用的内部大小。

创建 UICollectionView 的子类并重写以下方法

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

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

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

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

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

到目前为止,我想到的最简单的方法是@igor,

在 tableviewcell 类中插入这个

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

当然,还要在单元格的类中用您的插座更改 Collectionviewout

Pablo 的解决方案对我来说不是很好,我有奇怪的视觉效果(CollectionView 没有正确调整)。

有效的方法是在 layoutSubviews()期间将 CollectionView 的高度约束(作为 NSLayoutConstraint)调整为 CollectionView contentSize。这是在对单元格应用自动布局时调用的方法。

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

罗密欧上面的答案(https://stackoverflow.com/a/33364092/2704206)极大地帮助了我解决了我的问题。然而,为了解决我的问题,我不得不做一些不同的事情。首先,我不必经常给 layoutIfNeeded()打电话。我只需要在 systemLayoutSizeFitting函数的 collectionView上调用它。

其次,我在表视图单元格中的集合视图上设置了自动布局约束,以便给它一些空白。因此,我必须减去前面和后面的边距从 targetSize.width时设置的 collectionView.frame的宽度。我还必须将顶部和底部的边距添加到返回值 CGSize的高度。

为了获得这些约束常量,我可以选择创建约束的出口,硬编码它们的常量,或者通过标识符查找它们。我决定使用第三个选项,以使我的自定义表视图单元格类容易重用。最后,这就是我所需要的一切,让它运转起来:

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

作为通过标识符检索约束的助手函数,我添加了以下扩展:

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

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

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

我最近也面临着同样的问题,我几乎尝试了所有的解决方案,有些方案是有效的,有些则不是。我对 @ PabloRomeu方法的主要担心是,如果你在单元格中有其他内容(除了集合视图) ,你必须计算它们的高度和约束的高度,然后返回结果来获得正确的自动布局,我不喜欢在我的代码中手动计算东西。因此,这里的解决方案工作良好,我没有做任何手工计算在我的代码。

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

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
}

我认为这里发生的情况是,在计算了集合视图高度之后,我强制 tableview 单元格调整其高度。(在向数据源提供 CollectionView 日期之后)

我把所有的答案都读了一遍,这似乎适用于所有的案例。

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
}

我得到的想法从 @ Igor的职位和投资我的时间为我的项目与迅速

就这样穿过你的

 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
}

附加: 如果您在加载单元格时看到 UICollectionView 起伏不定。

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

names = data
contentView.layoutIfNeeded()
collectionviewNames.reloadData()
}

简短而甜蜜。请考虑 tableViewCell 类中的上述方法。您可能会从 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell调用它后,调出您的手机。在对集合视图调用 reloadData 之前,如果布局更新挂起,则需要在 tableCell 中告诉集合视图布局其子视图。