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

时间:2015-08-18 21:18:21

标签: uicollectionview ios9 uicollectionviewcell uicollectionviewlayout xcode7-beta5

  

2015-08-18 16:07:51.523示例[16070:269647]的行为   未定义UICollectionViewFlowLayout,因为:2015-08-18   16:07:51.523
  示例[16070:269647]项目宽度必须小于   UICollectionView的宽度减去左边的插图和   正确的值,减去内容的左右值   2015-08-18 16:07:51.524示例[16070:269647]相关   UICollectionViewFlowLayout实例是,它附加到;动画= {   位置=;   bounds.origin =;   bounds.size =; }; layer =; contentOffset:{0,0}; contentSize:{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中显示此错误消息,是否有人知道发生了什么以及是否有此修复?

Screenshot

21 个答案:

答案 0 :(得分:30)

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

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

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来消除它。 在这种情况下,当集合视图边界发生变化时,将始终调用流布局。

答案 1 :(得分:25)

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

答案 2 :(得分:11)

XCode 11

对我来说,原因是CollectionView的“估计”选项大小设置为“自动”。 看来这是XCode 11中的默认行为。

因此,我将图像加载到单元格中的原因是,由于图像的大小和自动选项(要根据图像的大小调整单元格的大小),因此单元格的宽度变得大于CollectionView的宽度。

通过将此选项设置为“无”即可解决问题。

where the automatic option is located

答案 3 :(得分:6)

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

原来,需要在“集合视图大小”面板中将“估计大小”设置为“无”。

请参阅:https://forums.developer.apple.com/thread/123625

答案 4 :(得分:5)

它的发生是因为你的集合视图单元格的宽度大于旋转后的集合视图宽度。假设你有一个1024x768的屏幕并且你的集合视图填满了屏幕。当你的设备是风景时,你的单元格' s宽度将是self.collectionView.frame.size.width - 20 = 1004并且它大于集合视图的纵向宽度= 768.调试器说"项目宽度必须小于UICollectionView的宽度减去左边和右边的值,减去左边和右边的内容值"。

答案 5 :(得分:5)

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

我能够通过两个步骤来修复它。

第1步:在UICollectionViewFlowLayout子类的prepareLayout()函数中,将super.prepareLayout()移至之后,其中{{1}已设置。我认为这会使超类使用正确的self.itemSize值。

itemSize

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

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

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

  // ...

}

现在警告消失了:)

一些注释:

  1. 确保您正在向collectionView添加约束而不使用 override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) collectionView.collectionViewLayout.invalidateLayout() }

  2. 请确保您没有在collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]中调用invalidateLayout,因为横向中边对边单元格的宽度大于纵向中集合视图的框架宽度。见以下参考文献。

  3. 参考

答案 6 :(得分:4)

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

答案 7 :(得分:3)

如果collectionView.contentOffset更改为否定,您可以检入调试器,在我的情况下,它会从(0,0)更改为(0,-40)。您可以使用此方法解决此问题

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

答案 8 :(得分:3)

这对我有用:

使旋转布局无效:

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

我认为必须注意这一点很重要,因为使布局无效不会立即触发重新计算像元大小,而是在下一个布局更新周期内。这意味着一旦最终调用了项目大小回调,则收集视图将具有正确的框架,从而实现准确的大小调整。

Voila!

答案 9 :(得分:2)

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

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

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

并且您的单元格实际上小于50x50点,然后UIKit打印出这些警告。 FWIW,最终调整了牢房的大小,并且警告似乎无害。

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

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

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

  

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

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

答案 10 :(得分:2)

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

我不知道你是否正在使用自动调整 - 知道我的理论/解决方案是否适用于每个人都会很有趣。

答案 11 :(得分:1)

我有类似的问题。

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

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

contextual prediction bar

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

由于您的问题似乎与宽度有关,而与细胞的高度无关,请检查contentInsetlayout.sectionInset值是否与您设置的值相同。< / p>

答案 12 :(得分:1)

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

您可以在情节提要中或以编程方式进行此操作。

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

答案 13 :(得分:1)

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

答案 14 :(得分: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

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

答案 15 :(得分:0)

我可以解决 super.prepare()  在 override func prepare() { }

答案 16 :(得分:0)

该解决方案肯定有效。尝试此代码

重写func viewWillTransition(大小为CGSize,协调器为:UIViewControllerTransitionCoordinator)

{

collectionView.collectionViewLayout.invalidateLayout()

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

}

答案 17 :(得分:0)

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

DispatchQueue.main.async {
    ...  
}

答案 18 :(得分:0)

我也一直在努力解决这个问题。

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

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

答案 19 :(得分:0)

就我而言,对于垂直 UICollectionView 我有:

get_template_part

我通过将水平插入固定到大小来修复它:

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

答案 20 :(得分:-3)

这就是你需要的:

`if(potionAttack == 20 & potionDefence == 20 & potionStrength == 20){
     System.out.println("You have reached the max amount of upgrades");
 } 
 else{
     cost = potionsUpgrade;
     gold = gold - cost;
 }`