我试图得到自我大小UICollectionViewCells工作与自动布局,但我似乎不能让单元格大小自己的内容。我很难理解如何从单元格的contentView内的内容更新单元格的大小。

下面是我尝试过的设置:

自定义UICollectionViewCell,在它的contentView中有一个UITextView。 UITextView的滚动被禁用。 contentView的水平约束是:"H:|[_textView(320)]",也就是说UITextView被固定在单元格的左边,显式宽度为320。 contentView的垂直约束是:"V:|-0-[_textView]",也就是说UITextView固定在单元格的顶部。 UITextView有一个高度约束设置为常数,UITextView报告将适合文本。

下面是单元格背景设置为红色,UITextView背景设置为蓝色时的效果:

我把我一直在GitHub上玩的项目放在这里。


当前回答

我尝试使用estimatedItemSize,但在插入和删除单元格时,如果estimatedItemSize不完全等于单元格的高度,则有一堆错误。我停止设置estimatedItemSize,并通过使用原型单元格实现动态单元格。这是如何做到的:

创建这个协议:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

在你的自定义UICollectionViewCell中实现这个协议:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

现在让你的控制器符合UICollectionViewDelegateFlowLayout,在它里面,有这个字段:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

它将被用作一个原型单元格来绑定数据,然后确定该数据如何影响您想要动态的维度

最后,必须实现uicollectionviewdelegatflowlayout的collectionView(:layout:sizeForItemAt:):

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

就是这样。在collectionView(:layout:sizeForItemAt:)中返回单元格的大小,以这种方式防止我不得不使用estimatedItemSize,并且插入和删除单元格工作得很好。

其他回答

我尝试使用estimatedItemSize,但在插入和删除单元格时,如果estimatedItemSize不完全等于单元格的高度,则有一堆错误。我停止设置estimatedItemSize,并通过使用原型单元格实现动态单元格。这是如何做到的:

创建这个协议:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

在你的自定义UICollectionViewCell中实现这个协议:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

现在让你的控制器符合UICollectionViewDelegateFlowLayout,在它里面,有这个字段:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

它将被用作一个原型单元格来绑定数据,然后确定该数据如何影响您想要动态的维度

最后,必须实现uicollectionviewdelegatflowlayout的collectionView(:layout:sizeForItemAt:):

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

就是这样。在collectionView(:layout:sizeForItemAt:)中返回单元格的大小,以这种方式防止我不得不使用estimatedItemSize,并且插入和删除单元格工作得很好。

解决方案包括3个简单步骤:

启用动态单元格大小

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

设置来自collectionView(:cellForItemAt:)的containerview . widthachor .constraint来限制contentView的宽度为collectionView的宽度。

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}

class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            containerViewWidthAnchor.constant = maxWidth
            containerViewWidthAnchor.isActive = true
        }
    }

    ....
}

因为你想要启用UITextView的自调整大小,它有一个额外的步骤来;

3.计算和设置高度tanchor。常量的UITextView。

当contentView的宽度被设置时,我们会在didSet的maxWidth中调整UITextView的高度。

UICollectionViewCell Inside job:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        containerViewWidthAnchor.constant = maxWidth
        containerViewWidthAnchor.isActive = true
        
        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

这些步骤会让你得到想要的结果。

完整的可运行的要旨

参考:Vadim Bulavin博客文章-集合视图单元格自我调整:一步一步的教程

截图:

对任何可能有帮助的人,

如果设置了estimatedItemSize,我就会发生严重的崩溃。即使我在numberOfItemsInSection中返回0。因此,单元格本身和它们的自动布局不是导致崩溃的原因……collectionView刚刚崩溃,即使是空的,这只是因为estimatedItemSize被设置为自大小。

在我的例子中,我重新组织了我的项目,从一个包含collectionView的控制器到一个collectionViewController,它工作了。

图。

对于那些尝试一切都没有运气的人来说,这是唯一让它为我工作的东西。 对于单元格内的多行标签,尝试添加以下神奇的行:

label.preferredMaxLayoutWidth = 200

更多信息:点击这里

干杯!

更新更多信息:

If you use flowLayout.estimatedItemSize, suggest use iOS8.3 later version. Before iOS8.3, it will crash [super layoutAttributesForElementsInRect:rect];. The error message is *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil' Second, in iOS8.x version, flowLayout.estimatedItemSize will cause different section inset setting did not work. i.e. function: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.