使用带有情节提要的 AutoLayout 的 UICollectionViewCell 的可变宽度和高度(没有 XIB)

Posted

技术标签:

【中文标题】使用带有情节提要的 AutoLayout 的 UICollectionViewCell 的可变宽度和高度(没有 XIB)【英文标题】:Variable width & height of UICollectionViewCell using AutoLayout with Storyboard (without XIB) 【发布时间】:2015-09-08 23:57:36 【问题描述】:

这是使用 AutoLayout、UICollectionView 和 UICollectionViewCell 的应用程序中的一个设计问题,它们根据 AutoLayout 约束及其内容(一些文本)自动调整宽度和高度。

它是一个 UITableView 列表,每个单元格都有自己的宽度和高度,每行根据其内容分别计算。它更像是内置于应用程序(或 WhatsUp)中的 ios 消息。

很明显,应用应该使用func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize

问题是在该方法中,应用程序不能调用func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCelldequeueReusableCellWithReuseIdentifier(identifier: String, forIndexPath indexPath: NSIndexPath!) -> AnyObject 来实例化单元格,用特定内容填充它并计算其宽度和高度。尝试这样做会导致无限期的递归调用或某些其他类型的应用程序崩溃(至少在 iOS 8.3 中)。

解决这种情况的最接近的方法似乎是将单元格的定义复制到视图层次结构中,以让自动布局自动调整“单元格”的大小(就像单元格与父集合视图具有相同的宽度),因此应用程序可以配置单元格具体内容并计算其大小。由于资源重复,这绝对不是修复它的唯一方法。

所有这些都与将 UILabel.preferredMaxLayoutWidth 设置为某个值有关,该值应该是 Auto-Layout 可控(非硬编码),可能取决于屏幕宽度和高度或至少由 Auto-layout 约束定义设置,因此应用程序可以获得计算多行 UILabel 固有大小。

我不想从 XIB 文件中实例化单元格,因为 Storyboard 应该是当今的行业标准,并且我希望减少对代码的干预。

编辑:

下面列出了无法运行的基本代码。因此,只有实例化变量 cellProbe(未使用)会使应用程序崩溃。没有那个调用,应用程序运行顺利。

var onceToken: dispatch_once_t = 0

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout 

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 1
    

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize 

        dispatch_once(&onceToken) 

            let cellProbe = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell
        

        return CGSize(width: 200,height: 50)
    

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell 

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell

        return cell
    

【问题讨论】:

【参考方案1】:

如果您使用约束,则无需为每个单元格设置大小。所以你应该删除你的委托方法func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize

相反,您需要在estimatedItemSize 中设置一个大小,方法是将其设置为CGSizeZero 以外的值,您是在告诉布局您还不知道确切的大小。然后布局会询问每个单元格的大小,并在需要时进行计算。

let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.estimatedItemSize = someReasonableEstimatedSize()

【讨论】:

preferredLayoutAttributesFittingAttributes(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! 很好,但问题是我实际上不知道单元格的大小。我需要有单元格实例,以便我可以用内容填充它以了解单元格的确切大小。它类似于 iOS 上的 iMessage,每条消息都有基于内容的高度。 对不起,我不明白这个问题。你有你的单元格实例,是的,你应该用内容填充它。您通常不需要覆盖preferredLayoutAttributesFittingAttributes(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! 默认实现将调整大小,您只会覆盖它来调整其他属性。在这一点上,我唯一的猜测是您可能正在使用没有 intrinsitContentSize 的子视图,例如如果你使用 UITextView 而不是 UILabel。 我刚刚再次阅读了您的原始问题,发现您正在尝试获取一个可以填充以计算大小的单元格。这很好,但你不能用你提到的任何一种方法来创建它。而是像创建任何其他视图一样创建它。实际上,您可以将所有单元格代码移动到常规 UIView 子类中,并将该子类设置为单元格的内容视图。然后你只是在处理一个视图,应该能够设置它的内容并获得它的大小。但是,您不需要对自定大小的单元格执行任何操作 - 这是我们在 iOS 7 之前所做的。 实际上,我使用 UILabel 作为尺寸驱动 (intrinsitContentSize) 视图,并且当它具有正确的内容设置时,我能够计算 UICollectionViewCell 的整体尺寸。 关于您的最新评论,如果我将在代码中构建单元格或将其定义以某种方式外部化,例如在视图层次结构中的单独 NIB 或模板单元格中,那将是正确的。但是,我在一个 Storyboard 中完全定义了应用程序设计,并且我希望在 UICollectionView 单元原型中可以有 cel 的定义。但是,似乎不可能以这种方式仍然根据可变内容进行单元格的高度计算...【参考方案2】:

就像使用自动布局动态调整表格视图单元格高度时一样,您不需要调用

func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat

正确的方法是创建一个本地静态单元格用于高度计算,就像自定义单元格的类方法

+ (CGFloat)cellHeightForContent:(id)content tableView:(UITableView *)tableView 
    static CustomCell *cell;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        cell = [tableView dequeueReusableCellWithIdentifier:[CustomCell cellIdentifier]];
    );

    Item *item = (Item *)content;
    configureBasicCell(cell, item);

    [cell setNeedsLayout];
    [cell layoutIfNeeded];
    CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height + 1.0f; // Add 1.0f for the cell separator height

我严重怀疑故事板是否是某种行业标准。 xib 是创建自定义单元格的最佳方式,包括 tableViewCell 和 CollectionViewCell

【讨论】:

感谢您的回答。我喜欢您通过在视图外观的早期实例化一次单元格的方式。我试图做类似的事情,但是当我从 viewWillAppear(animated: Bool) 中调用它时,我会崩溃,当我从 viewDidAppear(animated: Bool) 调用它时,有点太晚了,因为集合视图已经请求了单元格的高度但是在那个特定时间我没有。也许它需要在一些重载的 layoutSubviews() 函数调用中,或者通过首先让集合视图虚拟大小然后在应用加载单元格时调用 reloadData() 来做一些技巧。 @CROLakiluk 你不需要在视图中调用它会(did)出现它是一个类方法。编写自己的sizeForContent: 并在sizeForItemAtIndexPaht 中调用它 感谢您的帮助,但在带有流式布局的 UICollectionView 中,这种方法效果不佳。您可以查看已编辑的帖子...【参考方案3】:

要动态计算CollectionView 单元格的大小,只需将流布局属性设置为任意值:

 let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
 flowLayout.estimatedItemSize = CGSize(width: 0, height: 0)

【讨论】:

以上是关于使用带有情节提要的 AutoLayout 的 UICollectionViewCell 的可变宽度和高度(没有 XIB)的主要内容,如果未能解决你的问题,请参考以下文章

从 xib 或带有情节提要的其他场景添加子视图

带有自定义单元格的 ViewController 内的 UITableView 没有情节提要

使用情节提要的滚动视图

使用 Autolayout 根据宽度的比率设置 UIImage 高度

如何使用自动布局调整情节提要的图像大小?

如何使用 swift 使用带有 spriteKit 的情节提要