如何调整 UICollectionView 的高度为 UICollectionView 内容大小的高度?

How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?

在本例中,我希望 UICollectionView(红色的)缩小到内容大小的高度 UICollectionViewCells(黄色的),因为有很多空 space。我尝试的是使用:

override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
        self.invalidateIntrinsicContentSize()
    }
}

override var intrinsicContentSize: CGSize {
    return self.collection.contentSize
}

return self.collection.contentSize总是return(宽度,0) 由于这个原因,它缩小到高度值 30(我在 XIB 文件中设置的高度值,尽管我有 constaint >= 30)。

首先计算单元格的数量,然后将其乘以单元格的高度,然后return在此方法中计算高度

    collectionView.frame = CGRectMake (x,y,w,collectionView.collectionViewLayout.collectionViewContentSize.height); //objective c
    //[collectionView reloadData];
   collectionView.frame = CGRect(x: 0, y: 0, width: width, height: collectionView.collectionViewLayout.collectionViewContentSize.height) // swift

我建议如下:

  1. Add a height constraint to your collection view.
  2. Set its priority to 999.
  3. Set its constant to any value that makes it reasonably visible on the storyboard.
  4. Change the bottom equal constraint of the collection view to greater or equal.
  5. Connect the height constraint to an outlet.
  6. Every time you reload the data on the collection view do the following:

您可能还想考虑将集合视图的 Inset 添加到内容大小。

代码示例:

CGFloat height = myCollectionView.collectionViewLayout.collectionViewContentSize.height
heightConstraint.constant = height
self.view.setNeedsLayout() Or self.view.layoutIfNeeded()

说明:额外的,看懂了就不用看了。很明显!!

UI 将尝试反映所有约束,无论他们的优先级是什么。由于存在优先级较低的 (999) 的高度约束和大于或等于类型的底部约束。每当高度约束常量设置为小于父视图高度的值时,集合视图将等于给定高度,从而实现两个约束。

但是,当高度约束常量设置为大于父视图高度的值时,这两个约束都无法实现。因此,只会实现优先级较高的约束,即大于或等于底部约束。

以下只是经验猜测。因此,它实现了一个约束。但是,它还尝试使结果 UI 中的 error 对于其他未实现的较低优先级约束 尽可能低 .因此,集合视图的高度将等于父视图的大小。

您必须将高度限制设置为等于内容大小

HeightConstraint.constant = collection.contentSize.height 

执行以下操作。

  1. 首先为 UICollectionView
  2. 设置高度限制
  3. 这里calendarBaseViewHeightUICollectionView身高可变
  4. 重新加载集合视图后调用函数

    func resizeCollectionViewSize(){  
           calendarBaseViewHeight.constant = collectionView.contentSize.height      
     }
    

我最终将 UICollectionView 子类化并重写了一些方法,如下所示。

  • intrinsicContentSize 返回 self.collectionViewLayout.collectionViewContentSize 确保始终具有正确的尺寸
  • 然后只要它可能改变就调用它(比如 reloadData

代码:

override func reloadData() {
    super.reloadData()
    self.invalidateIntrinsicContentSize()
}

override var intrinsicContentSize: CGSize {
    return self.collectionViewLayout.collectionViewContentSize
}

但请注意,如果您显示大量数据,即使屏幕放不下,您也会失去 "cell re-using"。

1) 设置 CollectionView 的固定高度。

2) 创建此 CollectionView 高度常量的出口。 喜欢:

IBOutlet NSLayoutConstraint *constHeight;

3) 在您的 .m 文件中添加以下方法:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat height = collectionMenu.collectionViewLayout.collectionViewContentSize.height;
    constHeight.constant = height;
}

在您的 UICollectionView 上设置您的约束条件,例如 TrailingLeadingBottom:

如果您更详细地查看我的高度限制,因为它纯粹是为了看故事板,所以我不会出错,所以我要 Remove at build time。实际高度限制在我下面的代码中设置。

我的代码DrawerCollectionView,设置为集合视图Custom Class

import UIKit

class DrawerCollectionView: UICollectionView {

    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        heightAnchor.constraint(equalToConstant: contentSize.height).isActive = true
    }
}

在Swift5和Xcode10.2.1

我的 CollectionView 名称是 myCollectionView

  1. 修复 CollectionView 的高度

  2. 为您的 CollectionViewHeight 创建 Outlet

    IBOutlet weak var myCollectionViewHeight: NSLayoutConstraint!
    
  3. 使用下面的代码

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let height = myCollectionView.collectionViewLayout.collectionViewContentSize.height
        myCollectionViewHeight.constant = height
        self.view.layoutIfNeeded()
    }
    

基于文本内容的单元格动态宽度...

Dynamic cell width of UICollectionView depending on label width

为我工作

    let heightRes = resCollectionView.collectionViewLayout.collectionViewContentSize.height
    foodHeightConstrant.constant = height.advanced(by: 1 )

    foodCollectionView.setNeedsLayout()
    foodCollectionView.layoutIfNeeded()

采用了 d4Rk 的解决方案,这很棒,除了在我的情况下它会不断切断我的集合视图的底部(太短)。我发现这是因为内在内容大小有时为 0,这会影响计算。 IDK。我所知道的就是这个修复了它。

import UIKit

class SelfSizedCollectionView: UICollectionView {
    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: CGSize {
        let s = self.collectionViewLayout.collectionViewContentSize
        return CGSize(width: max(s.width, 1), height: max(s.height,1))
    }

}

获取单元格的高度。像这样

let cellHeight = cell.frame.height

获取集合视图的来源

let cvOrigin = collectionView.frame.origin

获取集合视图的宽度

let cvWidth = collectionView.bounds.width

设置内容视图的框架

collection.frame = CGRect(x: cvOrigin.x, y: cvOrigin.y, width: cvWidth, height: cellHeight )

这对我来说似乎是最简单的解决方案。

class SelfSizingCollectionView: UICollectionView {
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        isScrollEnabled = false
    }

    override var contentSize: CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }
}

您可能不需要覆盖 reloadData

  1. 子类 UICollectionView 如下
  2. 删除高度限制
  3. 开启内部尺寸

-

class ContentSizedCollectionView: UICollectionView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()            
        return CGSize(width: UIView.noIntrinsicMetric, height: collectionViewLayout.collectionViewContentSize.height)
    }
}

如果设置集合视图的高度限制。只需观察 viewDidLoad 中的 contentSize 变化并更新约束。

        self.contentSizeObservation = collectionView.observe(\.contentSize, options: [.initial, .new]) { [weak self] collectionView, change in
            guard let `self` = self else { return }
            guard self.collectionView.contentSize != .zero else { return }
            self.collectionViewHeightLayoutConstraint.constant = self.collectionView.contentSize.height
        }

我有一个多行、多选的 UICollectionView 子类,其中单元格的高度固定且左对齐,从左到右流动。它嵌入在垂直滚动视图内的垂直堆栈视图中。请参阅标签“属性 类型”下方的 UI 组件。

为了让集合视图适合其 contentSize 的高度,这是我必须做的(请注意,这都在 UICollectionView 子类中):

  1. 给集合视图一个非零的优先级最小高度约束999。将集合视图的大小自动调整为其内容高度在零高度下根本不起作用。

    let minimumHeight = heightAnchor.constraint(greaterThanOrEqualToConstant: 1)
    minimumHeight.priority = UILayoutPriority(999)
    minimumHeight.isActive = true
    
  2. 将垂直轴的集合视图的内容拥抱优先级设置为 .required

    setContentHuggingPriority(.required, for: .vertical)
    
  3. 调用 reloadData() 之后是以下调用:

    invalidateIntrinsicContentSize()
    setNeedsLayout()
    layoutIfNeeded()
    

    例如,我的子类中有一个setItems()函数:

    func setItems(_ items: [Item]) {
      self.items = items
      selectedIndices = []
      reloadData()
      invalidateIntrinsicContentSize()
      setNeedsLayout()
      layoutIfNeeded()
    }
    
  4. 覆盖 contentSizeintrinsicContentSize 如下:

    override var intrinsicContentSize: CGSize {
      return contentSize
    }
    
    override var contentSize: CGSize {
      didSet {
        invalidateIntrinsicContentSize()
        setNeedsLayout()
        layoutIfNeeded()
      }
    }
    

正在将 UICollectionView 的高度调整为其内容大小的高度

SWIFT 5

final class MyViewController: UIViewController {

    // it's important to declare layout as separate constant due to late update in viewDidLayoutSubviews()
    private let layout = UICollectionViewFlowLayout()
    private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)


    override func viewDidLoad() {
        super.viewDidLoad()
    
        setupCollectionView()
        setupCollectionViewConstraints()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        updateFlowLayout()
    }

    private func setupCollectionView() {
        view.addSubview(collectionView)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
        collectionView.dataSource = self
    }

    private func setupCollectionViewConstraints() {
        // your collectionView constraints setup
    }

    private func updateFlowLayout() {
        let height = collectionView.collectionViewLayout.collectionViewContentSize.height
        layout.itemSize = CGSize(width: view.frame.width, height: height)
        layout.scrollDirection = .horizontal
        layout.minimumInteritemSpacing = .zero
        layout.minimumLineSpacing = .zero
        layout.sectionInset = UIEdgeInsets.zero
    }
}

extension MyViewController: UICollectionViewDataSource {
     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {...}
     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {...}
}