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でのみこのエラーメッセージが表示されます。
これは、コレクションビューの幅が狭く(たとえば、ランドスケープモードからポートレートモードに変更)、セルが大きすぎて収まらない場合に発生します。
コレクションビューフローレイアウトを呼び出して適切なサイズを返す必要があるため、セルが大きくなりすぎているのはなぜですか?
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を返すだけで、それを取り除くことができます。その場合、コレクションビューの境界が変更されると、フローレイアウトが常に呼び出されます。
また、estimatedItemSizeをautomaticSizeに設定し、計算されたセルのサイズが50x50未満である場合にもこの現象が発生します(注:この回答はiOS 12の時点で有効でした。
つまり、推定アイテムサイズをautomaticSize
定数に設定して、セルの自己サイズ変更のサポートを宣言している場合:
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
セルが実際には50x50ポイントよりも小さい場合、UIKitはこれらの警告を出力します。 FWIW、セルは最終的に適切なサイズに設定され、警告は無害なようです。
1 修正する 回避策は、定数を1x1の推定アイテムサイズに置き換えることです。
flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)
estimatedItemSize
のドキュメントで次のように言及されているため、これは自己サイズ設定のサポートに影響を与えません。
それを他の値に設定すると、コレクションビューは、セルのpreferredLayoutAttributesFitting(_ :)メソッドを使用して、各セルの実際のサイズを照会します。
ただし、パフォーマンスへの影響がある場合とない場合があります。
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()
}
これで警告は消えました:)
CollectionViewに制約を追加し、collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
を使用していないことを確認してください
viewWillTransitionToSize()
のinvalidateLayout
を呼び出していないことを確認してください。横向きの端から端までのセルの幅は、縦向きのコレクションビューのフレーム幅よりも大きいためです。以下の参考文献を参照してください。
これは、回転後のコレクションビューセルの幅がコレクションビューの幅よりも大きいために発生します。1024x768の画面があり、コレクションビューが画面いっぱいになっているとします。デバイスが横長の場合、セルの幅はself.collectionView.frame.sizeになります。 .width-20 = 1004で、縦長のコレクションビューの幅= 768です。デバッガーは、「アイテムの幅は、UICollectionViewの幅からセクションインセットの左と右の値、コンテンツのインセットの左を引いた幅より小さくなければなりません」と正しい値」。
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();
}
いくつかの実験を行った後、これは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、すべての通常の容疑者でレイアウトを無効にしてみましたが、運はありませんでした。実際、invalidateLayout
beforesuper.willAppear()
を呼び出しても、警告が表示されます。
そして最終的に、この問題は、デリゲートがitemSizeを要求される前に更新されるcollectionView境界のサイズに関係しています。
それを念頭に置いて、collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
の代わりにAutoLayoutを使用してみたところ、問題は解決しました! (私はSnapKitを使用してAutoLayoutを実行しているので、髪の毛が常に抜けないようにしています)。また、invalidateLayout
内のviewWillTransition
(例にあるようにcoordinator.animate
なし)と、viewWillAppear(:)
の下部にあるinvalidateLayout
も必要です。それはseemsすべての状況をカバーします。
自動サイズ変更を使用しているかどうかはわかりません-私の理論/解決策がすべての人に役立つかどうかを知ることは興味深いでしょう。
私の場合、collectionView.contentOffset
が(0,0)
に変更された場合、(0,-40)
が負に変更されたかどうかをデバッガーで確認できます。この方法を使用すると、この問題を解決できます
if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
これは私のために働く:
回転時のレイアウトを無効にします:
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)
}
レイアウトを無効にしてもセルサイズの再計算はすぐにトリガーされないため、次のレイアウト更新サイクル中にこれが機能することに注意することが重要だと思います。これは、アイテムサイズコールバックが最終的に呼び出されると、コレクションビューに正しいフレームが設定されるため、正確なサイズ設定が可能になることを意味します。
ボイラー!
同様の問題がありました。
私の場合、コレクションビューがあり、セルの1つをタップすると、UITextField
が表示されたポップオーバーが開いてアイテムを編集しました。そのポップオーバーが消えた後、self.collectionView.contentInset.bottom
は55(元は0)に設定されました。
問題を解決するために、ポップオーバービューが消えた後、contentInsetを手動でUIEdgeInsetsZero
に設定しています。
元の問題は、キーボードの上部に表示されるコンテキスト予測バーに関連しているようです。キーボードが非表示になると、バーは消えますが、contentInset.bottom
値は元の値に復元されません。
問題はセルの高さではなく幅に関連していると思われるため、contentInset
またはlayout.sectionInset
の値のいずれかが設定した値と同じかどうかを確認してください。
同じ問題がありました。コレクションビューの制約をクリアし、ストーリーボードでリセットすることでこれを修正しました。
これは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() { }
を置くことで解決できました