the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width

2015-08-18 16:07:51.523 Example[16070:269647] the behavior of the UICollectionViewFlowLayout is not defined because: 2015-08-18 16:07:51.523
Example[16070:269647] the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
2015-08-18 16:07:51.524 Example[16070:269647] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7f24d670>, and it is attached to <UICollectionView: 0x7b377200; frame = (0 0; 768 1024); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7f24ce30>; animations = { position=<CABasicAnimation: 0x7f279d60>; bounds.origin=<CABasicAnimation: 0x7f279430>; bounds.size=<CABasicAnimation: 0x7f279480>; }; layer = <CALayer: 0x7aec9cf0>; contentOffset: {0, 0}; contentSize: {1024, 770}> collection view layout: <UICollectionViewFlowLayout: 0x7f24d670>. 2015-08-18 16:07:51.524 Example[16070:269647] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

That is what I get and what I do

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(self.collectionView!.frame.size.width - 20, 66)
}

I rotate from landscape to portrait then the console shows this error message only in iOS 9 if anyone knows what happens and if there is a fix for this?

Screenshot

94815 次浏览

我也有过类似的问题。

在我的例子中,我有一个集合视图,当您点击其中一个单元格时,打开一个带有 UITextField的弹出窗口来编辑项目。在弹出消失后,self.collectionView.contentInset.bottom被设置为55(最初为0)。

为了解决这个问题,在弹出窗口视图消失后,我手动将 contentInset 设置为 UIEdgeInsetsZero

contextual prediction bar

最初的问题似乎与显示在键盘上方的上下文预测栏有关。当键盘隐藏时,条会消失,但是 contentInset.bottom值不会恢复到原始值。

由于您的问题似乎与单元格的宽度有关,而与单元格的高度无关,所以请检查 contentInsetlayout.sectionInset值是否与您设置的值相同。

这是因为您的集合视图单元格的宽度在旋转后大于集合视图的宽度。假设您有一个1024x768的屏幕,并且您的集合视图填满了屏幕。当你的设备是横向的时候,你的单元格的宽度将是 self. CollectionView.frame.size.width-20 = 1004,并且它的宽度大于你的集合视图的宽度,纵向 = 768。调试器指出“项目宽度必须小于 UICollectionView 的宽度减去节插入的左值和右值,减去内容插入的左值和右值”。

我使用 UICollectionViewFlowLayout的子类并使用它的 itemSize属性来指定单元格大小(而不是 collectionView:sizeForItemAtIndexPath:委托方法)。每次我旋转屏幕到一个较短的宽度(例如横向-> 纵向)我得到这个巨大的警告问题。

我做了两步就修好了。

步骤1: UICollectionViewFlowLayout子类的 prepareLayout()函数中,将 super.prepareLayout()移动到设置 self.itemSize之后。我认为这使得超类使用正确的 itemSize值。

import UIKit


extension UICollectionViewFlowLayout {


var collectionViewWidthWithoutInsets: CGFloat {
get {
guard let collectionView = self.collectionView else { return 0 }
let collectionViewSize = collectionView.bounds.size
let widthWithoutInsets = collectionViewSize.width
- self.sectionInset.left - self.sectionInset.right
- collectionView.contentInset.left - collectionView.contentInset.right
return widthWithoutInsets
}
}


}


class StickyHeaderCollectionViewLayout: UICollectionViewFlowLayout {


// MARK: - Variables
let cellAspectRatio: CGFloat = 3/1


// MARK: - Layout
override func prepareLayout() {
self.scrollDirection = .Vertical
self.minimumInteritemSpacing = 1
self.minimumLineSpacing = 1
self.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: self.minimumLineSpacing, right: 0)
let collectionViewWidth = self.collectionView?.bounds.size.width ?? 0
self.headerReferenceSize = CGSize(width: collectionViewWidth, height: 40)


// cell size
let itemWidth = collectionViewWidthWithoutInsets
self.itemSize = CGSize(width: itemWidth, height: itemWidth/cellAspectRatio)


// Note: call super last if we set itemSize
super.prepareLayout()
}


// ...


}

注意,当屏幕旋转停止工作时,上面的更改将以某种方式使布局大小发生变化。这就是第二步的用武之地。

步骤2: 把这个放在保存集合视图的 视图控制器视图控制器中。

  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
collectionView.collectionViewLayout.invalidateLayout()
}

现在警告消失了:)


注意事项:

  1. 确保您正在向 CollectionView 添加约束,而没有使用 collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

  2. 确保您没有在 viewWillTransitionToSize()中调用 invalidateLayout,因为横向的边到边单元格的宽度比纵向的集合视图的框架宽度大。参见下面的参考文献。

参考文献

当您的集合视图调整到较小的宽度(例如,从横向模式到纵向模式) ,并且单元格变得太大而不能容纳时,就会发生这种情况。

为什么单元格变得太大了,因为应该调用集合视图流布局并返回合适的大小?

collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)

更新包括 Swift 4

@objc override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{  ...  }

这是因为这个函数是被 没有调用的,或者至少不是直接调用的。

发生的情况是,您的集合视图流布局子类不重写 shouldInvalidateLayoutForBoundsChange函数,默认情况下 returns false函数是这样的。

当此方法返回 false 时,集合视图首先尝试使用当前单元格大小,检测问题(记录警告) ,然后调用流布局来调整单元格大小。

这意味着两件事:

警告本身是无害的

2-您可以通过简单地覆盖 shouldInvalidateLayoutForBoundsChange函数来返回 true 来消除它。 在这种情况下,当集合视图边界发生更改时,将始终调用流布局。

你可以检查在调试器如果 collectionView.contentOffset改变为负在我的情况下,它从 (0,0)改变到 (0,-40)。您可以使用此方法解决此问题

if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}

在做了一些实验之后,这似乎也与您如何布局 CollectionView 有关。

Dr 是: 使用 AutoLayout,而不是 autoresizingMask。

因此,对于这个问题的核心,我找到的处理方向改变的最佳解决方案使用以下代码,这些代码都是有意义的:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)


coordinator.animate(alongsideTransition: { (context) in
self.collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}

但是 仍然存在可以获得项目大小警告的情况。对我来说,这是如果我在横向在一个标签,切换到另一个标签,旋转到肖像,然后返回到上一个标签。我试过在 will 显示,will 布局中使布局无效,所有常见的疑点,但没有运气。事实上,即使你调用 invalidateLayout 之前 super.willAppear()你仍然会得到警告。

最终,这个问题与在委托被要求输入 itemSize 之前 CollectionView 边界更新的大小有关。

所以我试着用 AutoLayout 代替 collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight],这样问题就解决了!(我使用 SnapKit 进行自动布局,这样我就不会不断地把头发拉出来)。您还需要在 viewWillTransition中使用 invalidateLayout(没有 coordinator.animate,因此就像您在示例中使用的那样) ,还需要在 viewWillAppear(:)的底部使用 invalidateLayout。这个 看起来可以覆盖所有情况。

我不知道你是否使用自动调整大小或没有-这将是有趣的,如果知道我的理论/解决方案工程的每个人。

我也有同样的问题。我通过清除收集视图中的约束并在 Storyboard 中重置它们来修复这个问题。

这就是你需要的:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//Get frame width
let width = self.view.frame.width
//I want a width of 418 and height of 274 (Aspect ratio 209:137) with a margin of 24 on each side of the cell (See insetForSectionAt 24 + 24 = 48). So, check if a have that much screen real estate.
if width > (418 + 48) {
//If I do return the size I want
return CGSize(width: 418, height: 274)
}else{
//Get new width. Frame width minus the margins I want to maintain
let newWidth = (width - 48)
//If not calculate the new height that maintains the aspect ratio I want. NewHeight = (originalHeight / originalWidth) * newWidth
let height = (274 / 418) * newWidth
//Return the new size that is Aspect ratio 209:137
return CGSize(width: newWidth, height: height)
}
}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 33
}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 33
}

我用 safeAreaLayoutGuide解决了这个问题。

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


return CGSize(width: (view.safeAreaLayoutGuide.layoutFrame.width), height: 80);
}

您还必须覆盖此功能,以正确支持纵向和横向模式。

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout();
}

这对我有用:

在轮换时使布局失效:

override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}

有一个单独的函数来计算可用宽度:

注意: 同时考虑到节的插入和内容的插入。

// MARK: - Helpers


func calculateCellWidth(for collectionView: UICollectionView, section: Int) -> CGFloat {
var width = collectionView.frame.width
let contentInset = collectionView.contentInset
width = width - contentInset.left - contentInset.right


// Uncomment the following two lines if you're adjusting section insets
// let sectionInset = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: section)
// width = width - sectionInset.left - sectionInset.right


// Uncomment if you are using the sectionInset property on flow layouts
// let sectionInset = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? UIEdgeInsets.zero
// width = width - sectionInset.left - sectionInset.right


return width
}

然后当然最后返回的项目大小:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: calculateCellWidth(for: collectionView, section: indexPath.section), height: 60)
}

我认为重要的是要注意,这是工作,因为无效的布局不会触发重新计算的单元格大小立即,但在下一个布局更新周期。这意味着一旦最终调用了项目大小回调,集合视图就有了正确的框架,从而允许精确调整大小。

瞧!

我也看到过这种情况,当我们将 EstiatedItemSize 设置为 AutomaticSize,并且计算出的单元格大小小于50x50时(注意: 这个答案在 iOS 12时是有效的。也许后来的版本已经修好了)。

例如,如果您通过将估计的项目大小设置为 automaticSize常数来声明对自调整单元格的支持:

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

你的细胞实际上小于50x50点,然后 UIKit 打印出这些警告。FWIW,细胞最终被适当的大小和警告似乎是无害的。

一种 修好变通方法是用估计的项目大小1x1来替换常数:

flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)

这并不影响自我调整规模的支持,因为正如 estimatedItemSize的文档所提到的:

将其设置为任何其他值将导致集合视图使用单元格的 preredLayoutAttributesFiting (_:)方法查询每个单元格的实际大小。

然而,它可能/可能-不具有性能影响。

我发现这对于 UICollectionViewController来说效果非常好,并且正确地制作了动画:

- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Ensure the layout is within the allowed size before animating below, or
//  `UICollectionViewFlowLayoutBreakForInvalidSizes` breakpoint will trigger
//  and the logging will occur.


if ([self.collectionView.collectionViewLayout isKindOfClass:[YourLayoutSubclass class]]) {
[(YourLayoutSubclass *)self.collectionView.collectionViewLayout updateForWidth:size.width];
}


// Then, animate alongside the transition, as you would:


[coordinator animateAlongsideTransition:^
(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
[self.collectionView.collectionViewLayout invalidateLayout];
}
completion:nil];


[super viewWillTransitionToSize:size
withTransitionCoordinator:coordinator];
}


///////////////


@implementation YourLayoutSubclass


- (void)prepareLayout {
[super prepareLayout];


[self updateForWidth:self.collectionView.bounds.size.width];
}


- (void)updateForWidth:(CGFloat)width {
// Update layout as you wish...
}


@end

... 请参阅上面的内联代码注释以获得解释。

我可以解决这个问题 super.prepare() 在... 的结尾 override func prepare() { }

这个解决方案肯定行得通. . 试试这个代码

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)


{


collectionView.collectionViewLayout.invalidateLayout()


super.viewWillTransition(to: size, with: coordinator)


}

将集合视图的估计大小“无”值从自动。

您可以通过故事板或编程方式来完成这项工作。

但是您必须使用自定义布局类或“ UICollectionViewGenerateFlowLayout”协议 API 手动计算它

对于 Xcode 11,上面的方法都不适合我。

事实证明,需要在集合视图大小面板中将 EstimateSize 设置为 Nothing。

见: https://forums.developer.apple.com/thread/123625

选择故事板中的集合视图到大小检查员更改估计大小为零

UIView 中的任何更改必须从主线程完成。将您的 CollectionView 的代码添加到视图层次结构中:

DispatchQueue.main.async {
...
}

我也为这个问题苦苦挣扎了好几个小时。

我的 UICollectionViewController嵌入在一个容器视图中,当将 iPad 从横向旋转到纵向时,它会显示错误。不过细胞确实变大了。

我通过在 UICollectionViewController中调用以下命令解决了这个错误:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
self.collectionView.collectionViewLayout.invalidateLayout()


super.viewWillTransition(to: size, with: coordinator)
}

“超级”必须被称为 之后,使布局失效。

此外,我需要在嵌入容器视图的视图中调用以下内容:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)


DispatchQueue.main.async {
self.collectionViewController.collectionView.reloadData()
}
}

这样,单元格在屏幕旋转完成后就会更新。

XCode 11

对我来说,原因是 CollectionView 的 Estimate 选项大小被设置为 auto。 这似乎是 XCode11中的默认行为。

因此,对我来说,我加载的图像在单元格中,因为图像的大小和自动选项(想要适应图像的大小的单元格的大小)单元格的宽度变得比 CollectionView 的宽度更大。

通过将此选项设置为“无”,问题得到了解决。

where the automatic option is located

在我的例子中,对于一个垂直的 UICollectionView,我有:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: padding(scale: 37))
}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: padding(scale: 10), left:  padding(scale: 4), bottom: padding(scale: 3), right: padding(scale: 4))
}

我通过将水平插入放置到大小来修正它:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.bounds.width - (2 * padding(scale: 4))), height: padding(scale: 37))
}

这就是我的解决办法:

  1. 从视图控制器中删除所有 UICollectionViewFlowCommittee 方法。

  2. 创建您自己的 flow Layout 类作为子类 UICollectionViewFlowLayout。

    类 ImageCollectionViewFlowLayout: UICollectionViewFlowLayout {}

  3. 将所有大小和其他参数配置到 ready ()方法中。

     private let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
    
    
    override func prepare() {
    super.prepare()
    
    
    guard let collectionView = collectionView else { return }
    self.sectionInset = sectionInsets
    self.minimumLineSpacing = sectionInsets.bottom
    self.minimumInteritemSpacing = sectionInsets.bottom
    let width = collectionView.bounds
    .inset(by: collectionView.layoutMargins)
    .inset(by: self.sectionInset)
    .width
    self.estimatedItemSize = CGSize(width: width, height: width + 16)
    //self.itemSize - I do not set itemsize because my cell has dynamical height
    }
    
  1. 在故事板(或 xib)上设置收集视图的新类。 enter image description here

就这样,保持代码清晰。

如果你是使用动态类型的字体,只需在字体大小改变时重新加载 collectionView:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
collectionView?.reloadData()
}

如果您在代码中设置 collectionViewLayoutitemSize,那么请确保大小是有效的。在设置 itemSize的代码行上设置一个断点,并检查大小是否符合错误警告的指示: 项目宽度必须小于 UICollectionView 的宽度减去节插入的左值和右值,减去内容插入的左值和右值。