UICollectionViewCell 的可变宽度和高度使用 AutoLayout 和 Storyboard(没有 XIB)

Variable width & height of UICollectionViewCell using AutoLayout with Storyboard (without XIB)

这是使用 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 中)。

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

所有这些都与将 UILabel.preferredMaxLayoutWidth 设置为某个值有关,该值应该是自动布局可控的(不是硬编码的),这可能取决于屏幕宽度和高度,或者至少由自动布局约束定义设置,因此应用可以计算多行 UILabel 固有大小。

我不想从 XIB 文件中实例化单元格,因为故事板应该是当今的行业标准,我希望对代码的干预尽可能少。

编辑:

不能运行的基本代码如下所列。因此,仅实例化变量 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
    }
}

就像动态调整大小 Table 使用自动布局查看单元格高度一样,您不需要调用

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

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

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

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

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

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

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

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

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