【问题标题】:UICollectionView inside a UITableViewCell -- dynamic height?UITableViewCell 内的 UICollectionView - 动态高度?
【发布时间】:2014-07-30 08:56:59
【问题描述】:

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

我们的首要 UIViewController 是在 Storyboards 中创建的,并且确实使用了自动布局。但是,我不知道如何根据UICollectionView的高度动态增加UITableViewCell的高度。

任何人都可以就如何做到这一点提供一些提示或建议吗?

【问题讨论】:

  • 您能详细介绍一下您要完成的布局吗? UITableViewCell 的高度是否与其实际的 tableview 不同? UICollectionViewUITableView 是否需要垂直滚动
  • UITableViewCell 的高度应该是动态的,基于作为表格视图单元格一部分的 UICollectionView 的高度。我的 UICollectionView 的布局是 4 列和动态行数。因此,对于 100 个项目,我的 UICollectionView 中有 4 列和 25 行。特定单元格的高度(如 UITableView 的 heightForRowAtIndexPath 所示)需要能够根据该 UICollectionView 的大小进行调整。是不是更清楚一点?
  • @Shadowman ,请建议我解决这种情况

标签: ios uitableview uicollectionview


【解决方案1】:

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

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

给定以下结构:

表格视图

TableViewCell

集合视图

CollectionViewCell

CollectionViewCell

CollectionViewCell

[...可变数量的单元格或不同的单元格大小]

解决方案是告诉自动布局首先计算 collectionViewCell 大小,然后是 collection view 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 的出口,使用 UICollectionViewFlowLayoutestimatedItemSize 启用 collectionView 单元格的自动布局。

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

注意:我尝试使用 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

记得在你的 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 帮助解决了这个问题

【讨论】:

  • 是的。这行得通。这应该是公认的答案。
  • 任何机构都可以为我提供一个示例代码吗?我想在垂直集合视图中显示多个图像,它位于 tableview 单元格内..
  • 最后这对我有用,但我不得不做另一个技巧:cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), 10000); 这将“模拟”具有长 collectionView 的长单元格,因此 collectionView 将计算其所有的大小细胞。否则,collectionView 只计算可见单元格大小,并错误地设置了 talbeViewCell 高度。
  • systemLayoutSizeFittingSize 中将帧高度设置为最大值时,我在重新加载单元格时遇到了一个问题,应用程序似乎在self.collectionView.layoutIfNeeded() 之后卡住了。我通过将框架高度设置为 1 而不是最大高度来修复它。如果有人遇到这个问题,希望它有所帮助。
  • 这是为我解决的问题 - 在 systemLayoutSizeFitting 中,将当前答案中显示的顺序颠倒,以便首先运行 collectionView.layoutIfNeeded(),然后运行 ​​collectionView.frame = CGRect.init(origin: .zero, size: CGSize.init(width: targetSize.width, height: 1))
【解决方案2】:

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

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

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

第 3 步。在tableView:cellForRowAtIndexPath: 添加代码:

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

【讨论】:

  • 你真的试过这个吗?似乎不可能同时在集合视图上设置高度约束并将其固定到表格视图单元格的边缘。您要么最终得到的约束太少(因此出现零大小警告),要么最终得到冲突的约束(其中一个被忽略)。请说明如何为此解决方案设置约束。
  • 我成功了。我将collectionView 的约束添加到UITableViewCell 的所有侧面并设置tableView.estimatedRowHeight = 44;tableView.rowHeight = UITableViewAutomaticDimension;
  • 这个像冠军一样工作......简单而高效......谢谢。如果对于某些人它不起作用,那么可能的原因可能是,我们需要将集合视图的底部绑定到表格视图单元格的底部。然后它将开始工作。快乐编码..:)
  • 在调整屏幕大小时,比如通过旋转 iPad,这会起作用吗?这可能会改变集合视图的高度,从而改变单元格的高度,但如果在屏幕上,表格视图不一定会重新加载单元格,因此永远不会调用 cellForRowAtIndexPath: 方法,也不会给你机会改变约束常数。不过可能会强制执行 reloadData。
  • 你试过不同的collectionView单元格的大小吗?那是我的问题。如果您有不同的collectionView 的单元格大小,则它不起作用... :(
【解决方案3】:

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

我建议您仅将集合视图用于所有目的。

您可以将其划分为多个部分,并将某些部分的布局“处理”为表格视图,而将其他部分的布局“处理”为集合视图。毕竟,没有什么是您无法使用集合视图实现的,而您可以使用表格视图来实现。

如果您的集合视图“部分”具有基本的网格布局,您还可以使用常规表格单元格来处理它们。不过,如果您不需要 iOS 5 支持,您最好使用集合视图。

【讨论】:

  • 您已将“treat”放在引号中,因为您不能为每个 CollectionView 部分定义不同的布局,对吧?我还没有找到为不同的集合视图部分定义不同布局的选项。我是否忽略了某些东西,或者您是否在谈论对我自己的 UICollectionViewLayout 自定义子类中的不同部分的“layoutAttributesForItemAtIndexPath”做出不同的响应?
  • 是 -> 你是说在我自己的 UICollectionViewLayout 的自定义子类中的不同部分对 layoutAttributesForItemAtIndexPath 的响应不同吗?
  • @Rivera,您的回答是最合理的,听起来也很正确。我在很多地方都读过这个建议。您是否还可以分享一些关于如何完成该操作或至少一些指示的示例或教程链接。 TIA
  • 您的答案假设您是从头开始,并且还没有需要适应的大型复杂表格视图。该问题专门询问了将collectionview放在tableview中。 IMO 您的回复不是“答案”
  • 这是一个糟糕的答案。在表视图中包含集合视图是很常见的事情,几乎每个应用程序都这样做。
【解决方案4】:

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

其次,我在表格视图单元格中的集合视图上设置了自动布局约束,以便为其提供一些填充。因此,在设置collectionView.frame 的宽度时,我必须从targetSize.width 中减去前导边距和尾随边距。我还必须将顶部和底部边距添加到返回值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 协议。希望这对其他人有帮助!

【讨论】:

    【解决方案5】:

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

    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
    }
    

    【讨论】:

      【解决方案6】:

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

      (您还应该重写相关方法:reloadSections、reloadItemsAtIndexPaths,方法类似于 reloadData())

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

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

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

      【讨论】:

      【解决方案7】:

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

      在你的 tableviewcell 类中插入这个

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

      当然,将collectionviewoutlet 更改为单元格类中的插座

      【讨论】:

      • 单元格的高度计算不正确
      【解决方案8】:

      我最近遇到了同样的问题,我几乎尝试了答案中的所有解决方案,其中一些有效,而另一些则不是我对 @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日期提供给数据源之后)

      【讨论】:

        【解决方案9】:

        我从@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
        }
        

        【讨论】:

        • 如果cellForRowAt被多次调用怎么办?
        【解决方案10】:

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

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

        【讨论】:

        • 对我来说,我试图在集合视图控制器中嵌入一个表视图控制器。每次重新使用一个单元格时,它最终都会记住我应用于它们的一些自动布局约束。这是非常繁琐的,最终是一个糟糕的主意。我想我将只使用一个集合视图。
        【解决方案11】:

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

        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 p。大于其内容高度;
        3. iconsCellHeight 是程序启动时的一个随机数(我知道,在纵向格式中它必须正好是 392,但这并不重要)。如果collectionView + 17 的内容不等于这个数字,那么改变它的值。下次在这种情况下,条件为 FALSE;
        4. 毕竟更新tableView。在我的情况下,它是两个操作的组合(用于很好地更新扩展行);
        5. 当然,在 heightForRowAtIndexPath 中添加一行代码。

        【讨论】:

          【解决方案12】:

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

          【讨论】:

            【解决方案13】:
            func configure(data: [Strings]) {
                    
                    names = data
                    contentView.layoutIfNeeded()
                    collectionviewNames.reloadData()
                }
            

            短而甜。在您的 tableViewCell 类中考虑上述方法。您可能会在将您的单元格出列后从func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 调用它。在您的集合视图上调用 reloadData 之前,在您的 tableCell 中,如果布局更新待处理,您需要告诉集合视图布局其子视图。

            【讨论】:

              【解决方案14】:

              在你的 UITableViewDelegate 中:

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

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

              self.items[indexPath.row].count
              

              或其他。

              【讨论】:

              • 我认为主要问题是如何在渲染之前计算collectionViewCellHeight
              【解决方案15】:

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

              【讨论】:

              • 这应该是最好的答案......因为如果不将内容放入虚拟单元格,则无法使用“uicollectionview”设置单元格的高度,获取高度,然后再次设置为正确的单元格
              • 我该如何使用该方法?
              • 添加示例。
              【解决方案16】:

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

              【讨论】:

                猜你喜欢
                • 2019-10-12
                • 1970-01-01
                • 2019-07-30
                • 2021-05-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-07-27
                • 1970-01-01
                相关资源
                最近更新 更多