80

2015-08-18 16:07:51.523 Example[16070:269647] UICollectionViewFlowLayout 的行为未定义,因为:2015-08-18 16:07:51.523
Example[16070:269647] 项目宽度必须小于UICollectionView 的宽度减去部分插入左右值,减去内容插入左右值。
2015-08-18 16:07:51.524 Example[16070:269647] 相关的 UICollectionViewFlowLayout 实例是 <UICollectionViewFlowLayout: 0x7f24d670>,它附加到 <UICollectionView: 0x7b377200; 帧 = (0 0; 768 1024); clipsToBounds = YES; 自动调整大小 = W+H;手势识别器 = <NSArray: 0x7f24ce30>; 动画 = { 位置=<CABasicAnimation: 0x7f279d60>; bounds.origin=<CABasicAnimation: 0x7f279430>; bounds.size=<CABasicAnimation: 0x7f279480>; }; 层 = <CALayer: 0x7aec9cf0>; 内容偏移:{0, 0}; contentSize: {1024, 770}> 集合视图布局:<UICollectionViewFlowLayout: 0x7f24d670>。2015-08-18 16:07:51.524 示例 [16070:269647] 在 UICollectionViewFlowLayoutBreakForInvalidSizes 处创建一个符号断点以在调试器中捕获此问题。

这就是我得到的和我所做的

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

我从横向旋转到纵向,然后控制台仅在 iOS 9 中显示此错误消息,如果有人知道会发生什么并且是否有解决此问题的方法?

截屏

4

22 回答 22

56

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

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

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 时,集合视图首先尝试使用当前单元格大小,检测到问题(记录警告),然后调用流布局来调整单元格大小。

这意味着两件事:

1 - 警告本身无害

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

于 2016-09-14T19:03:38.067 回答
53

在情节提要中选择collectionview 转到大小检查器将估计大小更改为无

于 2020-02-18T19:09:00.647 回答
27

X代码 11

对我来说,原因是 CollectionView 的 Estimate 选项大小设置为自动。这似乎是 XCode 11 中的默认行为。

因此,对于我在单元格中加载图像的我来说,由于图像的大小和自动选项(希望使单元格的大小与图像的大小相适应),单元格的宽度变得大于 CollectionView 的宽度。

通过将此选项设置为无,问题就解决了。

自动选项所在的位置

于 2020-08-20T20:39:58.990 回答
23

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

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

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

并且您的单元格实际上小于 50x50 点,然后 UIKit 会打印出这些警告。FWIW,单元格最终大小合适,警告似乎是无害的。

一种解决方法是用 1x1 的估计项目大小替换常量:

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

这不会影响自我调整大小的支持,因为作为estimatedItemSize提及的文档:

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

但是,它可能/可能不会对性能产生影响。

于 2018-10-25T15:48:58.357 回答
11

我已经通过使用解决了这个问题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();
}
于 2017-11-30T22:44:51.873 回答
7

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

我可以通过执行 2 个步骤来修复它。

步骤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 步的用武之地。

第 2 步:将其放入包含集合视图的视图控制器中。

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

现在警告消失了:)


一些注意事项:

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

  2. 确保您没有调用invalidateLayoutviewWillTransitionToSize() 因为横向中边到边单元格的宽度大于纵向中集合视图的框架宽度。请参阅以下参考资料。

参考

于 2016-03-18T05:57:11.280 回答
6

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

事实证明,需要在 collection view size 面板中将 Estimate Size 设置为 None。

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

于 2020-02-10T06:20:39.260 回答
5

发生这种情况是因为您的集合视图单元格的宽度大于旋转后的集合视图宽度。假设您有一个 1024x768 的屏幕并且您的集合视图填满了屏幕。当您的设备是横向时,您的单元格的宽度将为 self.collectionView.frame.size .width - 20 = 1004 并且它大于你的集合视图的纵向宽度 = 768。调试器说“项目宽度必须小于 UICollectionView 的宽度减去部分插入左右值,减去内容插入左侧和正确的价值观”。

于 2015-12-30T16:50:29.580 回答
4

这对我有用:

使旋转时的布局无效:

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

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

瞧!

于 2018-09-18T08:42:42.360 回答
4

将您的收藏视图的估计大小从自动变为“无”值。

您可以通过情节提要或以编程方式执行此操作。

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

于 2019-12-19T01:12:06.383 回答
3

您可以检查调试器是否collectionView.contentOffset更改为负数,在我的情况下它从更改(0,0)(0,-40). 您可以使用此方法解决此问题

if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
   self.automaticallyAdjustsScrollViewInsets = NO;
}
于 2016-09-25T08:46:36.293 回答
3

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

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

但是,在某些情况下您仍会收到项目大小警告。对我来说,如果我在一个选项卡中处于横向,切换到另一个选项卡,旋转到纵向,然后返回到上一个选项卡。我尝试在 willAppear、willLayout、所有常见的嫌疑人中使布局无效,但没有运气。事实上,即使你在收到警告invalidateLayout 之前打电话。 super.willAppear()

最终,此问题与在向委托询问 itemSize 之前更新 collectionView 边界的大小有关。

因此,考虑到这一点,我尝试使用 AutoLayout 而不是collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight],这样,问题就解决了!(我使用 SnapKit 进行自动布局,这样我就不会经常拔头发)。你也只需要invalidateLayoutin viewWillTransition(没有coordinator.animate, 所以就像你在你的例子中一样),并且invalidateLayoutviewWillAppear(:). 这似乎涵盖了所有情况。

我不知道您是否使用自动调整大小 - 知道我的理论/解决方案是否适用于所有人会很有趣。

于 2016-12-19T10:11:09.827 回答
2

这个解决方案绝对有效..试试这个代码

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

{

 collectionView.collectionViewLayout.invalidateLayout()

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

}
于 2019-12-15T14:38:51.317 回答
1

我可以通过放在 super.prepare() 末尾 来解决它override func prepare() { }

于 2019-08-02T05:56:53.757 回答
1

我遇到过同样的问题。我通过清除其约束的集合视图并在 Storyboard 中重置它们来解决此问题。

于 2017-06-10T16:26:21.870 回答
1

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

UICollectionViewController的嵌入在容器视图中,当将 iPad 从横向旋转到纵向时,它显示了错误。单元格确实调整了大小。

我通过在以下调用中解决了错误UICollectionViewController

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

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

在布局无效必须调用“super” 。

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

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

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

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

于 2020-06-17T08:20:56.377 回答
1

我有一个类似的问题。

在我的情况下,我有一个集合视图,当您点击其中一个单元格时,一个UITextField打开的弹出框可以编辑该项目。弹出框消失后,self.collectionView.contentInset.bottom设置为 55(最初为 0)。

为了解决我的问题,在弹出视图消失后,我手动将 contentInset 设置为UIEdgeInsetsZero.

上下文预测栏

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

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

于 2015-09-30T10:47:43.203 回答
0

我发现这非常适合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

...有关解释,请参见上面的内联代码注释。

于 2019-01-27T09:02:42.680 回答
0

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

DispatchQueue.main.async {
    ...  
}
于 2020-03-02T11:56:49.023 回答
0

就我而言,对于垂直 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))
}
于 2021-02-02T17:48:11.993 回答
0

这是我的解决方案:

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

  2. 创建自己的 flowLayout 类作为 UICollectionViewFlowLayout 的子类。

    类 ImageCollectionViewFlowLayout: UICollectionViewFlowLayout {}

  3. 将所有尺寸和其他参数配置到 prepare() 方法中。请参阅下面的示例。

     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)上为集合视图设置新类。 在此处输入图像描述

就这样。保持代码干净。

于 2021-08-21T05:29:22.870 回答
-4

这就是你需要的:

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
}
于 2017-08-13T00:30:18.173 回答