UIColViewView里面的UIColViewView - 动态高度?

时间:2014-06-09 18:39:45

标签: ios uitableview uicollectionview

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

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

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

17 个答案:

答案 0 :(得分:99)

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

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

鉴于以下结构:

  

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 是神奇的。它有一个outletView的插座,使用 UICollectionViewFlowLayout estimatedItemSize 为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;

}

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

答案 1 :(得分:67)

我认为,我的方式更简单,然后由@PabloRomeu提出。

步骤1.创建从UICollectionViewUITableViewCell subclass的插座,其中放置UICollectionView。我们的名字将是collectionView

步骤2.在IB中添加UICollectionView身高限制,并为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;

答案 2 :(得分:16)

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

我建议您仅使用集合视图来实现所有目的。

你可以将它分成几部分和"治疗"一些部分'布局作为表视图,其他布局作为集合视图。毕竟,您无法通过集合视图实现任何目标。

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

答案 3 :(得分:5)

Pablo Romeu上面的答案(https://stackoverflow.com/a/33364092/2704206)对我的问题帮助很大。然而,我必须以不同的方式做一些事情才能使我的问题得以解决。首先,我不必经常拨打layoutIfNeeded()。我只需要在collectionView函数的systemLayoutSizeFitting上调用它。

其次,我在表格视图单元格中对我的集合视图进行了自动布局约束,以给它一些填充。因此,在设置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作为集合视图的布局。最后,请务必将collectionView IBOutlet链接到故事板。

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

答案 4 :(得分:2)

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

答案 5 :(得分:2)

最简单的方法到目前为止,我已经提出了@igor回答上述信用,

在tableviewcell类中,只需插入

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

当然,在单元格的类

中更改collectionviewoutlet和你的插座

答案 6 :(得分:2)

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

另请注意,嵌入这些类型的viewControllers时可能会出现一些奇怪的行为。我曾经做过一次并且有一些奇怪的记忆问题,我从未解决过。

答案 7 :(得分:2)

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

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中添加一行代码。

答案 8 :(得分:1)

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

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
}

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

答案 12 :(得分:0)

func configure(data: [Strings]) {
        
        names = data
        contentView.layoutIfNeeded()
        collectionviewNames.reloadData()
    }

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

答案 13 :(得分:0)

 tempCell.frame = tableView.bounds;
 tempCell.layoutIfNeeded()
 tempCell.collectionView.reloadData()
 tempCell.heightConstForServiceType.constant = tempCell.collectionView.collectionViewLayout.collectionViewContentSize.height + 80.0;`enter code here`

答案 14 :(得分:-3)

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

答案 15 :(得分:-3)

在你的UITableViewDelegate中:

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

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

self.items[indexPath.row].count

或者其他什么。

答案 16 :(得分:-4)

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