web-dev-qa-db-ja.com

自動レイアウトのUICollectionViewセルフサイズセル

自動レイアウト機能を使ってUICollectionViewCellsを自動サイズ調整しようとしていますが、セルのサイズをコンテンツに合わせて調整することはできません。セルのcontentView内の内容からセルのサイズがどのように更新されるのかを理解できません。

これが私が試した設定です。

  • ContentView内にUICollectionViewCellを持つカスタムUITextView
  • UITextViewのスクロールは無効です。
  • ContentViewの水平方向の制約は "H:| [_textView(320)]"です。つまり、UITextViewは320の明示的な幅でセルの左側に固定されています。
  • ContentViewの垂直方向の制約は、 "V:| -0 - [_ textView]"、つまり、セルの上部に固定されているUITextViewです。
  • UITextViewは、UITextViewレポートがテキストに合うような高さ制約を定数に設定しています。

これは、セルの背景を赤に、UITextViewの背景を青に設定した場合の外観です。 cell background red, UITextView background blue

私は今までプレイしてきたプロジェクトをGitHub here に置きます。

171
rawbee

Swift 4用に更新

systemLayoutSizeFittingSizesystemLayoutSizeFittingに改名されました


IOS 9用にアップデート

私のGitHubソリューションがiOS 9の下で壊れるのを見た後、私はついに問題を完全に調査する時間を得ました。セルのサイズを変更するためのさまざまな構成の例をいくつか含むようにリポジトリを更新しました。私の結論は、自己サイジングセルは理論的には優れていますが、実際には面倒です。セルのサイズを変更するときに注意すること。

TL; DR

私の GitHubプロジェクトをチェックしてください


自己サイズ設定セルはフローレイアウトでのみサポートされているので、使用しているものがそれであることを確認してください。

セルのサイズを変更するためにセットアップする必要があるものが2つあります。

1. estimatedItemSizeUICollectionViewFlowLayoutに設定します

estimatedItemSizeプロパティを設定すると、フローレイアウトは本質的に動的になります。

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

2.セルサブクラスのサイズ変更のサポートを追加します

これには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ゲッターをオーバーライドさせて適切な特性を返すようにすることもできます。それはあなた次第です。

私が何かを逃したかどうか私に知らせてください、私が助けたそして幸運のコーディングを願っています


256
Daniel Galasko

Daniel Galaskoの答え へのいくつかの重要な変更は/ /私のすべての問題を修正しました。残念ながら、私は(まだ)直接コメントするのに十分な評判を持っていません。

ステップ1で、自動レイアウトを使用するときは、セルに親UIViewを1つ追加するだけです。セル内のすべてのものは、親のサブビューでなければなりません。それが私のすべての問題に答えた。 XcodeはUITableViewCellsにこれを自動的に追加しますが、UICollectionViewCellsには​​そうではありません(そうするべきです)。 docs :によると

セルの外観を設定するには、データアイテムのコンテンツをサブビューとして表示するために必要なビューをcontentViewプロパティのビューに追加します。セル自体に直接サブビューを追加しないでください。

その後、ステップ3を完全にスキップしてください。必要ありません。

39
Matt Koala

IOS10にはUICollectionViewFlowLayout.automaticSize(以前のUICollectionViewFlowLayoutAutomaticSize)と呼ばれる新しい定数があります。

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

あなたはこれを使用することができます:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

特にコレクションビューのセルの幅が一定の場合、パフォーマンスが向上します。

30
pawel221

IOS 10以降では、これは非常に簡単な2ステップのプロセスです。

  1. すべてのセルの内容が単一のUIView内(または自動レイアウトを非常に簡単にするUIStackViewのようなUIViewの子孫内)に配置されるようにします。 UITableViewCellsを動的にサイズ変更するのと同じように、ビュー階層全体で、最も外側のコンテナから最も内側のビューまで、制約を設定する必要があります。それはUICollectionViewCellと直接の子ビューの間の制約を含みます

  2. UICollectionViewのレイアウトを自動的にサイズ変更するように指示する

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    
27
Marmoy
  1. ViewDidLoad()にflowLayoutを追加します

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
    
  2. また、セルのmainContainerとしてUIViewを設定し、その中に必要なすべてのビューを追加します。

  3. 詳細については、この驚くべき驚くべきチュートリアルを参照してください: iOS 9および10でautolayoutを使用してセルを自動サイズ調整するUICollectionView

12
karenms

しばらくこれに苦労した後、私はあなたがスクロールを無効にしていない場合、サイズ変更はUITextViewsのために機能しないことに気づいた:

let textView = UITextView()
textView.scrollEnabled = false
5
noobular

コレクションビューのセルの高さを動的に変更しました。これは git hub repo です。

そして、preferredLayoutAttributesFittingAttributesが複数回呼び出される理由を調べてください。実際には、少なくとも3回呼ばれるでしょう。

コンソールログ画像: enter image description here

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が繰り返し検証されます。

2
Yang Young

UICollectionViewDelegateFlowLayoutメソッドを実装した場合:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

collectionview performBatchUpdates:completion:を呼び出すとき、サイズの高さはsizeForItemAtIndexPathの代わりにpreferredLayoutAttributesFittingAttributesを使います。

performBatchUpdates:completionのレンダリングプロセスはメソッドpreferredLayoutAttributesFittingAttributesを通りますが、それはあなたの変更を無視します。

1
Yang Young

だれでもそれを助けるかもしれません、

estimatedItemSizename__が設定されていると、厄介なクラッシュが起こりました。 numberOfItemsInSectionname__に0を返したとしても。そのため、セル自体とそれらの自動レイアウトはクラッシュの原因ではありませんでした... estimatedItemSizename__が自己サイズ調整用に設定されていたため、collectionViewが空の場合でもクラッシュしました。

私の場合は、collectionViewを含むコントローラからcollectionViewControllerにプロジェクトを再編成しましたが、うまくいきました。

図に行きます。

1
Jean Le Moignan

上記のメソッド例はコンパイルされません。これは修正されたバージョンです(しかしそれが動作するかどうかに関してはテストされていません)。

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
}
0
user3246173

詳細情報を更新してください。

  • 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:

0
Yang Young

上記の回答に加えて、

estimatedItemSizeプロパティのUICollectionViewFlowLayoutを何らかのサイズに設定し、 しない実装sizeForItem:atIndexPathデリゲートメソッド。

それでおしまい。

0
Murat Yasar