自動レイアウト機能を使ってUICollectionViewCells
を自動サイズ調整しようとしていますが、セルのサイズをコンテンツに合わせて調整することはできません。セルのcontentView内の内容からセルのサイズがどのように更新されるのかを理解できません。
これが私が試した設定です。
UICollectionViewCell
を持つカスタムUITextView
。UITextView
のスクロールは無効です。UITextView
は320の明示的な幅でセルの左側に固定されています。UITextView
です。UITextView
は、UITextView
レポートがテキストに合うような高さ制約を定数に設定しています。これは、セルの背景を赤に、UITextView
の背景を青に設定した場合の外観です。
私は今までプレイしてきたプロジェクトをGitHub here に置きます。
systemLayoutSizeFittingSize
はsystemLayoutSizeFitting
に改名されました
私のGitHubソリューションがiOS 9の下で壊れるのを見た後、私はついに問題を完全に調査する時間を得ました。セルのサイズを変更するためのさまざまな構成の例をいくつか含むようにリポジトリを更新しました。私の結論は、自己サイジングセルは理論的には優れていますが、実際には面倒です。セルのサイズを変更するときに注意すること。
TL; DR
自己サイズ設定セルはフローレイアウトでのみサポートされているので、使用しているものがそれであることを確認してください。
セルのサイズを変更するためにセットアップする必要があるものが2つあります。
estimatedItemSize
をUICollectionViewFlowLayout
に設定しますestimatedItemSize
プロパティを設定すると、フローレイアウトは本質的に動的になります。
self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
これには2つの種類があります。自動レイアウト または preferredLayoutAttributesFittingAttributes
のカスタムオーバーライド。
SO post セルに制約を設定することについてはすばらしいので、これについては詳しく説明しません。 Xcode 6が壊れた iOS 7でたくさんのものがあるので、iOS 7をサポートするなら、autoresizingMaskがセルのcontentViewに設定され、contentViewの境界が以下のように設定されるようにする必要があります。セルがロードされたときのセルの境界(つまりawakeFromNib
).
あなたが気をつけなければならないことはあなたのセルはTable View Cellよりもっと厳しく制限される必要があるということです。たとえば、幅を動的にしたい場合は、セルに高さの制約が必要です。同様に、高さを動的にしたい場合は、セルに幅の制約が必要になります。
preferredLayoutAttributesFittingAttributes
を実装するこの関数が呼び出されるとき、あなたのビューはすでにコンテンツで設定されています(つまりcellForItem
が呼び出されています)。あなたの制約が適切に設定されていると仮定すると、あなたはこのような実装を持つことができます:
//forces the system to do one layout pass
var isHeightCalculated: Bool = false
override func preferredLayoutAttributesFittingAttributes(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//Exhibit A - We need to cache our calculation to prevent a crash.
if !isHeightCalculated {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.width = CGFloat(ceilf(Float(size.width)))
layoutAttributes.frame = newFrame
isHeightCalculated = true
}
return layoutAttributes
}
_ note _ iOS 9では、振る舞いが少し変わったため、注意しないと実装がクラッシュする可能性があります(詳細は here を参照)。 preferredLayoutAttributesFittingAttributes
を実装するときは、レイアウト属性の枠を1回だけ変更するようにする必要があります。これを行わないと、レイアウトによって実装が無期限に呼び出され、最終的にクラッシュします。 1つの解決策は、計算されたサイズをセルにキャッシュし、isHeightCalculated
プロパティを使用して行ったようにセルを再利用するかその内容を変更するたびにこれを無効にすることです。
この時点で、collectionViewに '機能している'動的セルがあるはずです。私のテストでは、すぐに使える解決策がまだ十分に見つけられていないので、お気軽にコメントしてください。それでもUITableView
が動的サイジングIMHOのために戦いに勝ったように感じます。
もしあなたがprototypeItemSizeを計算するためにプロトタイプセルを使っているなら - これはあなたの XIBがサイズクラスを使っているなら - 壊れます 。これは、XIBからセルを読み込むときに、そのサイズクラスがUndefined
で設定されるためです。 iOS 7以降では、サイズクラスがデバイスに基づいてロードされるため(iPad = Regular-Any、iPhone = Compact-Any)、これはiOS 8以降でのみ機能しません。 XIBをロードせずにEstimatedItemSizeを設定するか、またはXIBからセルをロードしてcollectionViewに追加し(traitCollectionを設定)、レイアウトを実行してからスーパービューから削除することができます。あるいは、セルにtraitCollection
ゲッターをオーバーライドさせて適切な特性を返すようにすることもできます。それはあなた次第です。
私が何かを逃したかどうか私に知らせてください、私が助けたそして幸運のコーディングを願っています
Daniel Galaskoの答え へのいくつかの重要な変更は/ /私のすべての問題を修正しました。残念ながら、私は(まだ)直接コメントするのに十分な評判を持っていません。
ステップ1で、自動レイアウトを使用するときは、セルに親UIViewを1つ追加するだけです。セル内のすべてのものは、親のサブビューでなければなりません。それが私のすべての問題に答えた。 XcodeはUITableViewCellsにこれを自動的に追加しますが、UICollectionViewCellsにはそうではありません(そうするべきです)。 docs :によると
セルの外観を設定するには、データアイテムのコンテンツをサブビューとして表示するために必要なビューをcontentViewプロパティのビューに追加します。セル自体に直接サブビューを追加しないでください。
その後、ステップ3を完全にスキップしてください。必要ありません。
IOS10にはUICollectionViewFlowLayout.automaticSize
(以前のUICollectionViewFlowLayoutAutomaticSize
)と呼ばれる新しい定数があります。
self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
あなたはこれを使用することができます:
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
特にコレクションビューのセルの幅が一定の場合、パフォーマンスが向上します。
IOS 10以降では、これは非常に簡単な2ステップのプロセスです。
すべてのセルの内容が単一のUIView内(または自動レイアウトを非常に簡単にするUIStackViewのようなUIViewの子孫内)に配置されるようにします。 UITableViewCellsを動的にサイズ変更するのと同じように、ビュー階層全体で、最も外側のコンテナから最も内側のビューまで、制約を設定する必要があります。それはUICollectionViewCellと直接の子ビューの間の制約を含みます
UICollectionViewのレイアウトを自動的にサイズ変更するように指示する
yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
ViewDidLoad()にflowLayoutを追加します
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
}
}
また、セルのmainContainerとしてUIViewを設定し、その中に必要なすべてのビューを追加します。
詳細については、この驚くべき驚くべきチュートリアルを参照してください: iOS 9および10でautolayoutを使用してセルを自動サイズ調整するUICollectionView
しばらくこれに苦労した後、私はあなたがスクロールを無効にしていない場合、サイズ変更はUITextViewsのために機能しないことに気づいた:
let textView = UITextView()
textView.scrollEnabled = false
コレクションビューのセルの高さを動的に変更しました。これは git hub repo です。
そして、preferredLayoutAttributesFittingAttributesが複数回呼び出される理由を調べてください。実際には、少なくとも3回呼ばれるでしょう。
1番目のpreferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);
LayoutAttributes.frame.size.heightは現在のステータス 57.5 です。
2番目のpreferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);
予想どおり、セルフレームの高さは 534.5 に変わりました。しかし、コレクションビューはまだ高さゼロです。
3番目のpreferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);
コレクションビューの高さが0から477 に変更されたことがわかります。
動作はハンドルスクロールに似ています。
1. Before self-sizing cell
2. Validated self-sizing cell again after other cells recalculated.
3. Did changed self-sizing cell
最初は、このメソッドは一度だけ呼び出すと思いました。だから私は次のようにコーディングしました:
CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;
この行
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
システムコールが無限ループし、アプリがクラッシュします。
サイズが変更されると、すべてのセルの位置(フレーム)が変更されなくなるまで、すべてのセルのpreferredLayoutAttributesFittingAttributesが繰り返し検証されます。
UICollectionViewDelegateFlowLayoutメソッドを実装した場合:
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath
collectionview performBatchUpdates:completion:
を呼び出すとき、サイズの高さはsizeForItemAtIndexPath
の代わりにpreferredLayoutAttributesFittingAttributes
を使います。
performBatchUpdates:completion
のレンダリングプロセスはメソッドpreferredLayoutAttributesFittingAttributes
を通りますが、それはあなたの変更を無視します。
だれでもそれを助けるかもしれません、
estimatedItemSize
name__が設定されていると、厄介なクラッシュが起こりました。 numberOfItemsInSection
name__に0を返したとしても。そのため、セル自体とそれらの自動レイアウトはクラッシュの原因ではありませんでした... estimatedItemSize
name__が自己サイズ調整用に設定されていたため、collectionViewが空の場合でもクラッシュしました。
私の場合は、collectionViewを含むコントローラからcollectionViewControllerにプロジェクトを再編成しましたが、うまくいきました。
図に行きます。
上記のメソッド例はコンパイルされません。これは修正されたバージョンです(しかしそれが動作するかどうかに関してはテストされていません)。
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
{
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes
var newFrame = attr.frame
self.frame = newFrame
self.setNeedsLayout()
self.layoutIfNeeded()
let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
newFrame.size.height = desiredHeight
attr.frame = newFrame
return attr
}
詳細情報を更新してください。
flowLayout.estimatedItemSize
を使用する場合は、iOS8.3以降のバージョンを使用することをお勧めします。 iOS8.3より前では、[super layoutAttributesForElementsInRect:rect];
がクラッシュします。エラーメッセージは
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
第二に、iOS8.xバージョンでは、flowLayout.estimatedItemSize
は別のセクションインセット設定を機能させませんでした。すなわち機能:(UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:
。
上記の回答に加えて、
estimatedItemSizeプロパティのUICollectionViewFlowLayoutを何らかのサイズに設定し、 しない実装sizeForItem:atIndexPathデリゲートメソッド。
それでおしまい。