web-dev-qa-db-ja.com

セルの幅がcollectionViewの幅より大きいため、UICollectionViewFlowLayoutの動作は定義されていません

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インスタンスはです。アニメーション= {position =; bounds.Origin =; bounds.size =; };レイヤー=; 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

42

これは、コレクションビューの幅が狭く(たとえば、ランドスケープモードからポートレートモードに変更)、セルが大きすぎて収まらない場合に発生します。

コレクションビューフローレイアウトを呼び出して適切なサイズを返す必要があるため、セルが大きくなりすぎているのはなぜですか?

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

Swift 4を含むように更新

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

これは、この関数がnotと呼ばれるか、少なくともすぐには呼び出されないためです。

起こるのは、コレクションビューフローレイアウトサブクラスがshouldInvalidateLayoutForBoundsChange関数(デフォルトではreturns false)をオーバーライドしないことです。

このメソッドがfalseを返すと、コレクションビューは最初に現在のセルサイズを使用しようとし、問題(警告をログに記録する)を検出し、フローレイアウトを呼び出してセルのサイズを変更します。

これは2つのことを意味します:

1-警告自体は有害ではありません

2-shouldInvalidateLayoutForBoundsChange関数をオーバーライドしてtrueを返すだけで、それを取り除くことができます。その場合、コレクションビューの境界が変更されると、フローレイアウトが常に呼び出されます。

34
AirXygène

また、estimatedItemSizeをautomaticSizeに設定し、計算されたセルのサイズが50x50未満である場合にもこの現象が発生します(注:この回答はiOS 12の時点で有効でした。

つまり、推定アイテムサイズをautomaticSize定数に設定して、セルの自己サイズ変更のサポートを宣言している場合:

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

セルが実際には50x50ポイントよりも小さい場合、UIKitはこれらの警告を出力します。 FWIW、セルは最終的に適切なサイズに設定され、警告は無害なようです。

修正する 回避策は、定数を1x1の推定アイテムサイズに置き換えることです。

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

estimatedItemSizeのドキュメントで次のように言及されているため、これは自己サイズ設定のサポートに影響を与えません。

それを他の値に設定すると、コレクションビューは、セルのpreferredLayoutAttributesFitting(_ :)メソッドを使用して、各セルの実際のサイズを照会します。

ただし、パフォーマンスへの影響がある場合とない場合があります。

7
Manav

UICollectionViewFlowLayoutのサブクラスを使用し、そのitemSizeプロパティを使用して(collectionView:sizeForItemAtIndexPath:デリゲートメソッドの代わりに)セルサイズを指定しています。画面をより短い幅(たとえば、横長->縦長)に回転するたびに、この大きな警告が表示されます。

2つのステップを実行することで修正できました。

ステップ1:UICollectionViewFlowLayoutサブクラスのprepareLayout()関数で、super.prepareLayout()afterに移動する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:これを、コレクションビューを保持するview controllerに入れます。

  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を呼び出していないことを確認してください。横向きの端から端までのセルの幅は、縦向きのコレクションビューのフレーム幅よりも大きいためです。以下の参考文献を参照してください。

参照資料

6
Hlung

これは、回転後のコレクションビューセルの幅がコレクションビューの幅よりも大きいために発生します。1024x768の画面があり、コレクションビューが画面いっぱいになっているとします。デバイスが横長の場合、セルの幅はself.collectionView.frame.sizeになります。 .width-20 = 1004で、縦長のコレクションビューの幅= 768です。デバッガーは、「アイテムの幅は、UICollectionViewの幅からセクションインセットの左と右の値、コンテンツのインセットの左を引いた幅より小さくなければなりません」と正しい値」。

5

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();
}
4
Luis Santiago

いくつかの実験を行った後、これはcollectionViewのレイアウト方法にも関係しているようです。

Tl; drは:autoresizingMaskではなくAutoLayoutを使用します。

そのため、問題の核心について、向きの変更を処理するために見つけた最良の解決策は、次のコードを使用します。

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、すべての通常の容疑者でレイアウトを無効にしてみましたが、運はありませんでした。実際、invalidateLayoutbeforesuper.willAppear()を呼び出しても、警告が表示されます。

そして最終的に、この問題は、デリゲートがitemSizeを要求される前に更新されるcollectionView境界のサイズに関係しています。

それを念頭に置いて、collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]の代わりにAutoLayoutを使用してみたところ、問題は解決しました! (私はSnapKitを使用してAutoLayoutを実行しているので、髪の毛が常に抜けないようにしています)。また、invalidateLayout内のviewWillTransition(例にあるようにcoordinator.animateなし)と、viewWillAppear(:)の下部にあるinvalidateLayoutも必要です。それはseemsすべての状況をカバーします。

自動サイズ変更を使用しているかどうかはわかりません-私の理論/解決策がすべての人に役立つかどうかを知ることは興味深いでしょう。

3
Loz

私の場合、collectionView.contentOffset(0,0)に変更された場合、(0,-40)が負に変更されたかどうかをデバッガーで確認できます。この方法を使用すると、この問題を解決できます

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

これは私のために働く:

回転時のレイアウトを無効にします:

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

レイアウトを無効にしてもセルサイズの再計算はすぐにトリガーされないため、次のレイアウト更新サイクル中にこれが機能することに注意することが重要だと思います。これは、アイテムサイズコールバックが最終的に呼び出されると、コレクションビューに正しいフレームが設定されるため、正確なサイズ設定が可能になることを意味します。

ボイラー!

3
josh-fuggle

同様の問題がありました。

私の場合、コレクションビューがあり、セルの1つをタップすると、UITextFieldが表示されたポップオーバーが開いてアイテムを編集しました。そのポップオーバーが消えた後、self.collectionView.contentInset.bottomは55(元は0)に設定されました。

問題を解決するために、ポップオーバービューが消えた後、contentInsetを手動でUIEdgeInsetsZeroに設定しています。

contextual prediction bar

元の問題は、キーボードの上部に表示されるコンテキスト予測バーに関連しているようです。キーボードが非表示になると、バーは消えますが、contentInset.bottom値は元の値に復元されません。

問題はセルの高さではなく幅に関連していると思われるため、contentInsetまたはlayout.sectionInsetの値のいずれかが設定した値と同じかどうかを確認してください。

1
Marius

同じ問題がありました。コレクションビューの制約をクリアし、ストーリーボードでリセットすることでこれを修正しました。

1
Alien

これは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

...説明については、上記のインラインコードコメントを参照してください。 ????????

0
Ben Guild

super.prepare()の最後にoverride func prepare() { }を置くことで解決できました

0
Wasim