如何用 FlowLayout 确定 UICollectionView 的高度

我有一个 UICollectionView与一个 UICollectionViewFlowLayout,我想计算其内容大小(返回在 intrinsicContentSize需要通过自动布局调整其高度)。

问题是: 即使对所有单元格都有一个固定且相等的高度,我也不知道在 UICollectionView中有多少“行”/行。由于表示数据项的单元格的宽度不同,所以我也不能通过数据源中的项数来确定这个计数,因此 UICollectionView的一行中的项数也不同。

由于我在官方文件中找不到关于这个话题的任何提示,而且谷歌也没有带给我任何进一步的信息,所以任何帮助和想法都将非常感谢。

98199 次浏览

哇!出于某种原因,经过几个小时的研究,我现在找到了一个非常简单的答案: 我完全在错误的地方搜索,挖掘了所有我能在 UICollectionView上找到的文档。

简单易行的解决方案在于底层布局: 只需在 myCollectionView.collectionViewLayout属性上调用 collectionViewContentSize,就可以得到内容的高度和宽度作为 CGSize。就这么简单。

如果您使用的是 自动布局,那么您可以创建 UICollectionView的子类

如果使用下面的代码,则不必为集合视图指定任何高度约束,因为它将根据集合视图的内容而变化。

执行情况如下:

@interface DynamicCollectionView : UICollectionView


@end


@implementation DynamicCollectionView


- (void) layoutSubviews
{
[super layoutSubviews];


if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
{
[self invalidateIntrinsicContentSize];
}
}


- (CGSize)intrinsicContentSize
{
CGSize intrinsicContentSize = self.contentSize;


return intrinsicContentSize;
}


@end

viewDidAppear,你可以通过:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

也许当你重新加载数据,然后需要计算一个新的高度与新的数据,然后你可以得到它: 当你的 CollectionViewviewdidload完成重新加载数据时,添加观察者来监听:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

然后添加下面的函数来获得新的高度或者在 Collectionview 完成重新加载之后做任何事情:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
//Whatever you do here when the reloadData finished
float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;
}

别忘了移除观察者:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];

用户1046037在 Swift 中回答..。

class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if bounds.size != intrinsicContentSize() {
invalidateIntrinsicContentSize()
}
}


override func intrinsicContentSize() -> CGSize {
return self.contentSize
}
}

用户的 Swift 3代码1046037答案

import UIKit


class DynamicCollectionView: UICollectionView {


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


}


override var intrinsicContentSize: CGSize {
return contentSize
}


}

现在回答这个问题已经太晚了,但是我最近已经讨论过这个问题了。李的 以上答案帮了我很大的忙才得到了我的答案。但是这个答案仅限于 目标 c,我当时在 迅速工作。
@ lee 关于你的回答,请允许我让快速用户轻松解决这个问题。为此,请遵循以下步骤: < br >

在声明部分声明一个 CGFloat变量:

var height : CGFloat!

viewDidAppear,你可以通过:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

也许当你的 reload数据,然后需要计算一个新的高度与新的数据,然后你可以得到它通过: addObserver听当你的 CollectionView完成重新加载数据在 viewWillAppear:

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
....
....
self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
}

然后添加波纹管函数,以获得新的高度或做任何事情后,collectionview完成重新加载:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height


var frame : CGRect! = self.myCollectionView.frame
frame.size.height = newHeight


self.myCollectionView.frame = frame
}

别忘了移除观察者:

override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
....
....
self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

我希望这将有助于你迅速解决你的问题。

快速版@user1046037:

public class DynamicCollectionView: UICollectionView {


override public func layoutSubviews() {
super.layoutSubviews()
if !bounds.size.equalTo(intrinsicContentSize) {
invalidateIntrinsicContentSize()
}
}


override public var intrinsicContentSize: CGSize {
let intrinsicContentSize: CGSize = contentSize
return intrinsicContentSize
}
}

Swift 4

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


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

此外,你最好在得到高度之前先拨打 reloadData:

theCollectionView.collectionViewLayout.collectionViewContentSize;

Swift 4.2

class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if bounds.size != intrinsicContentSize {
invalidateIntrinsicContentSize()
}
}


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

Swift 4.2,Xcode 10.1,iOS 12.1:

由于某种原因,collectionView.contentSize.height看起来比我的集合视图的解析高度要小。首先,我使用了一个相对于 Superview 高度1/2的自动布局约束。为了解决这个问题,我将约束更改为相对于视图的“安全区域”。

这允许我使用 collectionView.contentSize.height设置单元格高度来填充我的集合视图:

private func setCellSize() {
let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)


let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: width, height: height)
}

之前

height constraint relative to superview bad simulator result

之后

height constraint relative to safe area good simulator result

这个答案是基于@Er.Vihar 的答案。你可以通过使用 RxSwift 轻松地实现这个解决方案

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
print("sizer \(size)")
}).disposed(by: rx.disposeBag)

假设您的 CollectionView 名为 CollectionView,那么您可以采用如下高度。

let height = collectionView.collectionViewLayout.collectionViewContentSize.height