如何调整UICollectionView的高度,使其与UICollectionView的内容大小相同?

126

我希望UICollectionView(红色)在这种情况下能够缩小到内容大小的高度,即UICollectionViewCells(黄色)的高度,因为有很多空白空间。我尝试使用以下方法:

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总是返回(宽度,0),因此它缩小得太多,只有高度30的值(我在XIB文件中为高度设置了constaint >= 30)。 enter image description here

2
你需要获取CollectionView的高度约束输出,并像这样调整高度:heightConstraint.constant = collectionView.contentSize.height - dahiya_boy
你正在使用自定义布局还是由苹果提供的UICollectionViewFlowLayout? - Georgi Boyadzhiev
我正在使用默认的UICollectionViewFlowLayout。 - charbinary
您应该使用collectionViewLayout.collectionViewContentSize,可以参考@hasan83的答案。这是一个可能性。 - Georgi Boyadzhiev
18个回答

224

我建议以下操作:

  1. 给你的集合视图(collection view)添加一个高度约束。
  2. 将其优先级设置为999。
  3. 将其常量设置为任何使其在Storyboard中合理可见的值。
  4. 将集合视图的底部等于约束改为大于或等于。
  5. 将高度约束连接到输出口。
  6. 每次重新加载集合视图上的数据时,请执行以下操作:

您还可以通过将其添加到内容大小来考虑集合视图的插入(Inset)。

代码示例:

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

解释:额外内容,如果你已经理解了,就不用读这部分啦!

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

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

以下只是来自经验的猜测。所以,它实现了一个约束。但是,它也试图使产生的 UI 中未实现的优先级较低的约束 尽可能地低错误率。因此,集合视图的高度将等于父视图大小。


@MiteshDobareeya 嗯,也许是与集合视图的页眉和页脚有关?您在Storyboard中添加了任何视图到集合视图中吗? - hasan
或者可能是第4点。将集合视图的底部等式约束改为大于或等于。大于或等于意味着集合视图的高度始终应小于或等于其父视图的高度。 - hasan
@hasan83 我没有在集合视图中添加任何视图,也没有添加任何页眉和页脚。在我的设计中,我只有一个滚动视图,集合视图位于滚动视图的内容视图和另外两个视图内。所有视图都有固定高度,集合视图有固定高度出口。因此,当我重新加载集合视图时,我只是根据其内容获取高度并将其分配给其高度常量。但不幸的是,会得到一些额外的高度。 - Mitesh Dobareeya
1
就像评论区中其他人所说的,使用这种方法时会出现一些额外的高度。为了解决这个问题,我只需在集合视图的performBatchUpdates中执行.reloadData(),然后更改高度约束的常量即可。 - Carl
我使用RxSwift来检测集合视图高度的变化(例如,当通过在更新相关数据后调用reloadData进行配置时)。在由观察者调用的闭包中,我会更新高度约束并在父视图上调用layoutIfNeeded。观察者的代码如下:collectionView.rx.observe(CGSize.self, "contentSize").subscribe(onNext: {(size) in <你的代码> } - Andy Weinstein
显示剩余11条评论

35

在Swift 5和Xcode 10.2.1中,我的CollectionView名称是myCollectionView

  1. 为您的CollectionView设置固定高度

  2. 为您的CollectionViewHeight创建Outlet

IBOutlet weak var myCollectionViewHeight: NSLayoutConstraint!
  • 使用以下代码

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

    根据文本内容设置单元格的动态宽度...

    UICollectionView动态单元格宽度取决于标签宽度


    你的代码中的myCollectionView是什么意思?你在哪里声明了它? - Karthi Rasu
    @Karthi Rasu,myCollectionView是您的集合视图高度约束。如果您正在使用Storyboard,请固定collectionView的高度。 - Naresh
    2
    完美解决方案!! - Vinayak Bhor
    动态宽度的单元格... https://dev59.com/-mAg5IYBdhLWcg3w0Nze#68907152 - Naresh

    23

    1) 设置 CollectionView 的固定高度。

    2) 创建此 CollectionView 高度常量的 Outlet。 如:

    IBOutlet NSLayoutConstraint *constHeight;

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

    - (void)viewDidLayoutSubviews {
        [super viewDidLayoutSubviews];
    
        CGFloat height = collectionMenu.collectionViewLayout.collectionViewContentSize.height;
        constHeight.constant = height;
    }
    

    3
    这个可以在 Swift 5Xcode 10.2.1 上运行。谢谢。 - Pankaj Kulkarni

    21

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

    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


    4
    这是在iOS 14上对我有效的唯一方法。谢谢! - Teddy K
    谢谢!到目前为止最好的一个! - Abdul Karim Khan

    20

    我最终通过子类化 UICollectionView 并重写以下方法来解决问题。

    • intrinsicContentSize设置为self.collectionViewLayout.collectionViewContentSize,以确保始终具有正确的大小
    • 然后只需在可能更改时(如在reloadData时)调用它即可。

    代码:

    override func reloadData() {
        super.reloadData()
        self.invalidateIntrinsicContentSize()
    }
    
    override var intrinsicContentSize: CGSize {
        return self.collectionViewLayout.collectionViewContentSize
    }
    

    但需要注意的是,如果您显示大量数据,即使它们不适合屏幕,也会失去“单元格重用”功能。


    2
    到目前为止,这是最美的解决方案 - 至少对于我的用例来说。谢谢。它解决了时间、布局需求以及所有其他约束和视图大小以确定内容大小的问题 - 这个只需要工作就可以了。 - n13
    2
    实际上,我必须将内在大小限制为至少1,而不是0,才能使其正确工作。否则,在计算中会出现小错误,导致视图底部被剪切几个像素。 - n13
    为什么这意味着您失去了单元格重用?使用此方法导致这种情况的原因不清楚。 - AnthonyMDev
    1
    @AnthonyMDev 因为使用这种方法,你正在调整集合视图的大小以同时适应所有项目,因此所有项目都会在任何时候加载。 - d4Rk
    啊,这很有道理。我以为它会破坏实际的出队逻辑,但事实上集合视图在任何时候都会显示所有单元格。感谢您的回复! - AnthonyMDev
    显示剩余4条评论

    9
    您需要将高度约束设置为与内容大小相等。
    HeightConstraint.constant = collection.contentSize.height 
    

    8

    采用了d4Rk的解决方案,这很棒,但在我的情况下,它会不断切断我的集合视图底部(太短了)。我发现这是因为内在内容大小有时为0,这会使计算失准。我不知道。所有我知道的是这个问题被解决了。

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

    这很奇怪,我不知道为什么需要先设置第一个值为1才能开始计算最终值。 - cristian_064

    6
    1. 将 UICollectionView 子类化如下
    2. 如果有的话,删除高度约束
    3. 启用内在大小(Intrinsic Size)

    -

    class ContentSizedCollectionView: UICollectionView {
        override var contentSize:CGSize {
            didSet {
                invalidateIntrinsicContentSize()
            }
        }
    
        override var intrinsicContentSize: CGSize {
            layoutIfNeeded()            
            return CGSize(width: UIView.noIntrinsicMetric, height: collectionViewLayout.collectionViewContentSize.height)
        }
    }
    

    4

    我有一个多行、多选的UICollectionView子类,其中单元格具有固定的高度,并从左到右左对齐流式布局。它嵌入在一个垂直堆栈视图中,该视图位于垂直滚动视图中。请参见“属性类型”标签下面的UI组件。

    enter image description here

    为了使集合视图适应其contentSize的高度,在UICollectionView子类中执行以下操作(请注意,这些操作都在其中):

    1. Give the collection view a non-zero minimum height constraint of priority 999. Auto-sizing the collection view to its content height simply won't work with zero height.

      let minimumHeight = heightAnchor.constraint(greaterThanOrEqualToConstant: 1)
      minimumHeight.priority = UILayoutPriority(999)
      minimumHeight.isActive = true
      
    2. Set the collection view's content hugging priority to .required for the vertical axis.

      setContentHuggingPriority(.required, for: .vertical)
      
    3. Calling reloadData() is followed by the following calls:

      invalidateIntrinsicContentSize()
      setNeedsLayout()
      layoutIfNeeded()
      

      For example, I have a setItems() function in my subclass:

      func setItems(_ items: [Item]) {
        self.items = items
        selectedIndices = []
        reloadData()
        invalidateIntrinsicContentSize()
        setNeedsLayout()
        layoutIfNeeded()
      }
      
    4. Override contentSize and intrinsicContentSize as follows:

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

    1
    你能分享完整的代码吗? - iosMentalist

    4
    如果您设置了集合视图的高度约束,请在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
            }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接