あなたが求めているのは、UICollectionViewを使用してUITableViewのようなレイアウトを作成する方法であるように思えます。それが本当に必要な場合、これを行う正しい方法は、カスタムUICollectionViewLayoutサブクラス(おそらくSBTableLayoutのようなもの)を使用することです。
一方、デフォルトのUICollectionViewFlowLayoutを使用してこれを行うクリーンな方法があるかどうかを本当に質問している場合は、方法がないと思います。iOS8のセルフサイジングセルを使用しても、簡単ではありません。あなたが言うように、根本的な問題は、フローレイアウトの機構が1つの次元を修正して別の次元を応答させる方法を提供しないことです。(さらに、可能であれば、複数行のラベルのサイズを設定するために2つのレイアウトパスが必要になると、さらに複雑になります。これは、systemLayoutSizeFittingSizeへの1回の呼び出しでセルフサイズセルがすべてのサイズを計算する方法と一致しない場合があります。)
ただし、独自のサイズを決定し、コレクションビューの幅に自然に応答するセルを使用して、フローレイアウトでテーブルビューのようなレイアウトを作成する場合は、もちろんそれが可能です。まだ厄介な方法があります。「サイズ変更セル」、つまり、コントローラーがセルサイズの計算のためにのみ保持する非表示のUICollectionViewCellでそれを実行しました。
このアプローチには2つの部分があります。最初の部分は、コレクションビューの幅を取り込み、サイズ変更セルを使用してセルの高さを計算することにより、コレクションビューデリゲートが正しいセルサイズを計算することです。
UICollectionViewDelegateFlowLayoutで、次のようなメソッドを実装します。
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
これにより、コレクションビューは囲んでいるコレクションビューに基づいてセルの幅を設定しますが、サイズ変更セルに高さを計算するように要求します。
2番目の部分は、サイジングセルが独自のAL制約を使用して高さを計算するようにすることです。複数行のUILabelが2段階のレイアウトプロセスを効果的に必要とするため、これは必要以上に難しい場合があります。作業はメソッドpreferredLayoutSizeFittingSize
で行われ、次のようになります。
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(このコードは、「Advanced Collection View」のWWDC2014セッションのサンプルコードのAppleサンプルコードを基にしています。)
いくつか注意すべき点があります。ラベルの幅を計算および設定するために、layoutIfNeeded()を使用してセル全体のレイアウトを強制しています。しかし、それだけでは不十分です。またpreferredMaxLayoutWidth
、ラベルが自動レイアウトでその幅を使用するように設定する必要があると思います。そして、それから初めてsystemLayoutSizeFittingSize
、ラベルを考慮しながらセルにその高さを計算させることができます。
このアプローチは好きですか?番号!!複雑すぎると感じ、レイアウトを2度行います。ただし、パフォーマンスが問題にならない限り、コードで2回定義する必要があるよりも、実行時にレイアウトを2回実行する方がよいでしょう。
私の希望は、最終的にはセルのサイズを変更するセルの動作が異なり、これによりすべてがはるかに簡単になることです。
仕事でそれを示すサンプルプロジェクト。
しかし、なぜセルフサイジングセルを使用しないのですか?
理論的には、iOS8の「セルのセルフサイジング」のための新しい機能により、これは不要になるはずです。自動レイアウト(AL)を使用してセルを定義した場合、コレクションビューはそれ自体がサイズ調整され、正しくレイアウトされるのに十分なほどスマートでなければなりません。実際には、これを複数行ラベルで機能させる例は見たことがありません。これは、セルのサイズを自動調整するメカニズムにまだバグがあるためだと思います。
しかし、それは主に自動レイアウトとラベルの通常の扱いやすさによるものだと思います。つまり、UILabelsは基本的に2段階のレイアウトプロセスを必要とするということです。セルのサイズをセルで両方の手順を実行する方法はわかりません。
そして私が言ったように、これは実際には別のレイアウトの仕事です。幅を固定して高さを選択できるようにするのではなく、サイズのあるものを配置することはフローレイアウトの本質の一部です。
そして、preferredLayoutAttributesFittingAttributes:はどうですか?
そのpreferredLayoutAttributesFittingAttributes:
方法は赤ニシンだと思います。それだけが、新しいセルフサイジングセルメカニズムで使用されます。したがって、そのメカニズムが信頼できない限り、これは答えではありません。
そして、systemlayoutSizeFittingSize :?
あなたは正しい、ドキュメントは混乱しています。
systemLayoutSizeFittingSize:
およびsystemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
両方のドキュメントは、UILayoutFittingCompressedSize
とUILayoutFittingExpandedSize
としてのみ渡す必要があることを示唆していますtargetSize
。ただし、メソッドシグネチャ自体、ヘッダーコメント、および関数の動作は、それらがtargetSize
パラメーターの正確な値に応答していることを示しています。
実際、を設定UICollectionViewFlowLayoutDelegate.estimatedItemSize
すると、新しいセルフサイジングセルメカニズムを有効にするために、その値がtargetSizeとして渡されるようです。そしてUILabel.systemLayoutSizeFittingSize
とまったく同じ値を返すようUILabel.sizeThatFits
です。への引数systemLayoutSizeFittingSize
は大まかなターゲットであるとsizeThatFits:
想定され、への引数は最大の外接サイズであると想定されているため、これは疑わしいものです。
その他のリソース
そのような日常的な必要条件が「研究リソース」を必要とするべきだと考えるのは悲しいことですが、私はそう思うと思います。良い例と議論は: