未定义 UICollectionViewFlowLayout 的行为,因为单元格宽度大于 collectionView 宽度

Posted

技术标签:

【中文标题】未定义 UICollectionViewFlowLayout 的行为,因为单元格宽度大于 collectionView 宽度【英文标题】:the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width 【发布时间】:2015-11-11 23:58:30 【问题描述】:

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 示例[16070:269647] 相关 UICollectionViewFlowLayout 实例是 ,并附加到 ;动画 = 位置=; bounds.origin=; bounds.size=; ;层 = ;内容偏移:0, 0;内容大小:1024, 770> 集合视图布局:。 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 中显示此错误消息,如果有人知道会发生什么并且是否有解决此问题的方法?

【问题讨论】:

您的问题解决了吗? 不,它没有解决 【参考方案1】:

这是我的解决方案:

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

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

    类 ImageCollectionViewFlowLayout:UICollectionViewFlowLayout

    将所有尺寸和其他参数配置到 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
     
    
    在情节提要(或 xib)上为集合视图设置新类。

就是这样。保持代码干净。

【讨论】:

【参考方案2】:

在我的例子中,我有一个垂直的 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))

【讨论】:

【参考方案3】:

XCode 11

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

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

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

【讨论】:

救世主!!对于 Xcode 11,这是最简单和完美的技巧。【参考方案4】:

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

我的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()
    

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

【讨论】:

【参考方案5】:

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

DispatchQueue.main.async 
    ...  

【讨论】:

【参考方案6】:

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

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



 collectionView.collectionViewLayout.invalidateLayout()

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


【讨论】:

【参考方案7】:

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

【讨论】:

完美运行。并且还从 CollectionViewCell 的大小检查器中勾选内容视图 我在两个 iOS 版本 10 和 11 中崩溃了,@satish 的解决方案对我有用。 你救了我!两个小时试图修复这些错误,这个解决方案通过点击解决了所有问题 谢谢,这行得通。但是你能解释一下为什么会这样吗?我有默认值:自动,为什么不起作用?【参考方案8】:

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

原来需要在集合视图大小面板中将 Estimate Size 设置为 None。

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

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

【参考方案10】:

我可以通过放置来解决它 super.prepare() 在结束时 override func prepare()

【讨论】:

【参考方案11】:

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

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

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 - 警告本身无害

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

【讨论】:

我已经尝试了互联网上所有可能的建议,这个是正确的答案!!!非常感谢您的详细解释和简单的解决方案!如果可以的话,我会为这个答案投票 10 次。 谢谢,这对我有用。 swift 4 的更新:将“sizeForItemAtIndexPath”更改为“sizeForItem”,否则它将不起作用,并且不会显示任何错误。 从 WWDC'18 UICollectionView 之旅 中提到,即使在 滚动 集合视图时也会调用此方法 shouldInvalidateLayoutForBoundsChange。所以演讲者特别提醒你在重写这个方法时要小心,因为它经常被调用。 这里有一个对我有用的问题的替代解决方案:***.com/a/53117524/5242940 这听起来确实是对的,但我实现了这个覆盖以返回 true,设置自定义布局类,并在 iOS 12.2 模拟器中运行它,看到它调用了我的覆盖,但仍然反复发出警告。 【参考方案12】:

我发现这对 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

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

【讨论】:

【参考方案13】:

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

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

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

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

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

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

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

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

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

【讨论】:

如果这些天 covid-19 不和我们在一起,我发誓我会去任何你现在的地方旅行,拥抱你,哈哈,你救了我的一天,这让我发疯了。查看了很多设置自定尺寸单元的教程,甚至在我自己的项目中也正确设置了它们,但是......它们都超过了 50pt。非常感谢! 你已经说过了,这一点非常明显!感谢您提供此答案 谢谢!这个变量显然不是automaticSize,而是standardEstimatedSize。为什么以这种方式命名变量是我无法理解的。【参考方案14】:

这对我有用:

旋转时布局无效:

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)

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

瞧!

【讨论】:

您好,这条线出错了:let sectionInset = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: section)。你有使它工作的示例项目吗?谢谢 嘿@LêKhánhVinh,那是因为你没有实现那个特定的可选委托方法。您可以将这些行注释掉,我也会更新我的帖子。 您好,要根据内容自动调整单元格的高度,而不是返回固定值 60? @LêKhánhVinh 是的,有一种方法可以通过集合视图来实现。您可以研究“自我调整大小的单元格”和 UICollectionViewFlowLayoutAutomaticSize。 只有写视图才会布局子视图方法帮助我解决了这个问题。 +1【参考方案15】:

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

我可以通过两个步骤来修复它。

第 1 步:UICollectionViewFlowLayout 子类的prepareLayout() 函数中,将super.prepareLayout() 移动到设置self.itemSizeafter。我认为这使得超类使用正确的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()
  

现在警告消失了:)


一些注意事项:

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

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

参考文献

Making Adaptive Forms using UICollectionView Collection view with self-sizing cells

【讨论】:

不看这个解决方案,答案比我从项目中出来要晚很多:( 那行得通@Hlung,我希望你不介意,我已经添加了一些注释,因为还有其他要求来防止此类错误。 修复警告的关键是在 itemSize 设置后调用 [super prepareLayout]。谢谢@Hlung!【参考方案16】:

我已经使用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();

【讨论】:

【参考方案17】:

这是你需要的:

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

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

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

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

【讨论】:

【参考方案20】:

在做了一些实验之后,这似乎也与你如何布局你的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 进行自动布局,这样我就不会经常拔头发)。您还只需要在viewWillTransition 中输入invalidateLayout(没有coordinator.animate,就像您在示例中一样),以及invalidateLayoutviewWillAppear(:) 的底部。这似乎涵盖了所有情况。

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

【讨论】:

我设法通过使 viewWillLayoutSubviews 中的布局无效来解决 ``` override open func viewWillLayoutSubviews() super.viewWillLayoutSubviews() self.collectionView.collectionViewLayout.invalidateLayout() ```跨度> 【参考方案21】:

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

【讨论】:

@Magoo 您可以通过更改项目大小的集合视图流布局值来修复它。代表必须考虑轮换。正如调试器所说,项目宽度必须小于 UICollectionView 的宽度减去 section insets 左右值,减去 content insets 左右值。【参考方案22】:

我也有类似的问题。

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

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

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

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

【讨论】:

以上是关于未定义 UICollectionViewFlowLayout 的行为,因为单元格宽度大于 collectionView 宽度的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionViewDelegateFlowLayout UICollectionViewFlowLayout 的区别

尽管实现了 sizeForItemAt,但 UICollectionViewFlowLayout 单元格大小永远不会改变

在发生自调整大小之前,如何准确地为 UICollectionViewLayout 提供矩形中的元素?

“注意:未定义的变量”、“注意:未定义的索引”、“警告:未定义的数组键”和“注意:未定义的偏移量”使用 PHP

“注意:未定义的变量”、“注意:未定义的索引”、“警告:未定义的数组键”和“注意:未定义的偏移量”使用 PHP

“注意:未定义的变量”、“注意:未定义的索引”、“警告:未定义的数组键”和“注意:未定义的偏移量”使用 PHP