UICollectionView Self Sizing Cells with Auto Layout

前端 未结 16 1721
野性不改
野性不改 2020-11-22 05:58

I\'m trying to get self sizing UICollectionViewCells working with Auto Layout, but I can\'t seem to get the cells to size themselves to the content. I\'m having

相关标签:
16条回答
  • 2020-11-22 06:01

    contentView anchor mystery:

    In one bizarre case this

        contentView.translatesAutoresizingMaskIntoConstraints = false
    

    would not work. Added four explicit anchors to the contentView and it worked.

    class AnnoyingCell: UICollectionViewCell {
        
        @IBOutlet var word: UILabel!
        
        override init(frame: CGRect) {
            super.init(frame: frame); common() }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder); common() }
        
        private func common() {
            contentView.translatesAutoresizingMaskIntoConstraints = false
            
            NSLayoutConstraint.activate([
                contentView.leftAnchor.constraint(equalTo: leftAnchor),
                contentView.rightAnchor.constraint(equalTo: rightAnchor),
                contentView.topAnchor.constraint(equalTo: topAnchor),
                contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
            ])
        }
    }
    

    and as usual

        estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    

    in YourLayout: UICollectionViewFlowLayout

    Who knows? Might help someone.

    Credit

    https://www.vadimbulavin.com/collection-view-cells-self-sizing/

    stumbled on to the tip there - never saw it anywhere else in all the 1000s articles on this.

    0 讨论(0)
  • 2020-11-22 06:03

    If you implement UICollectionViewDelegateFlowLayout method:

    - (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

    When you call collectionview performBatchUpdates:completion:, the size height will use sizeForItemAtIndexPath instead of preferredLayoutAttributesFittingAttributes.

    The rendering process of performBatchUpdates:completion will go through the method preferredLayoutAttributesFittingAttributes but it ignores your changes.

    0 讨论(0)
  • 2020-11-22 06:04

    Update more information:

    • 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:.

    0 讨论(0)
  • 2020-11-22 06:05

    In addition to above answers,

    Just make sure you set estimatedItemSize property of UICollectionViewFlowLayout to some size and do not implement sizeForItem:atIndexPath delegate method.

    That's it.

    0 讨论(0)
  • 2020-11-22 06:05

    For anyone who tried everything without luck, this is the only thing that got it working for me. For the multiline labels inside cell, try adding this magic line:

    label.preferredMaxLayoutWidth = 200
    

    More info: here

    Cheers!

    0 讨论(0)
  • 2020-11-22 06:05

    I tried using estimatedItemSize but there were a bunch of bugs when inserting and deleting cells if the estimatedItemSize was not exactly equal to the cell's height. i stopped setting estimatedItemSize and implemented dynamic cell's by using a prototype cell. here's how that's done:

    create this protocol:

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

    implement this protocol in your custom 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
        }
    }
    

    now make your controller conform to UICollectionViewDelegateFlowLayout, and in it, have this field:

    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
    }
    

    it will be used as a prototype cell to bind data to and then determine how that data affected the dimension that you want to be dynamic

    finally, the UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:) has to be implemented:

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

    and that's it. Returning the cell's size in collectionView(:layout:sizeForItemAt:) in this way preventing me from having to use estimatedItemSize, and inserting and deleting cells works perfectly.

    0 讨论(0)
提交回复
热议问题