[解決済み] UICollectionViewでオートレイアウトによるセルの1次元指定
質問
iOS 8では
UICollectionViewFlowLayout
は、自身のコンテンツのサイズに基づいて自動的にセルのサイズを変更することをサポートしています。これにより、セルの幅と高さの両方がコンテンツに応じてリサイズされます。
すべてのセルの幅(または高さ)に固定値を指定し、他の寸法はリサイズできるようにすることは可能ですか?
簡単な例として、セル内に複数行のラベルがあり、それをセルの側面に配置する制約があるとします。複数行のラベルは、テキストを収容するためにさまざまな方法でサイズ変更することができます。セルはコレクションビューの幅を満たし、それに応じて高さを調整する必要があります。その代わり、セルのサイズは無秩序に変更され、セルのサイズがコレクション ビューのスクロールできない寸法よりも大きいと、クラッシュすることさえあります。
iOS 8 ではメソッド
systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority:
コレクション ビューの各セルについて、レイアウトはセル上でこのメソッドを呼び出し、推定サイズを渡します。私にとって意味のあることは、セルでこのメソッドをオーバーライドして、指定されたサイズを渡し、水平方向の制約を必須に設定し、垂直方向の制約に低い優先度を設定することです。この方法では、水平方向のサイズはレイアウトで設定された値に固定され、垂直方向のサイズは柔軟に変更することができます。
このような感じです。
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return attributes;
}
しかし、このメソッドによって返されるサイズはまったく奇妙なものです。このメソッドに関するドキュメントは私には非常に不明瞭で、定数の使用について言及しています。
UILayoutFittingCompressedSize
UILayoutFittingExpandedSize
という定数を使っていますが、これは単にゼロサイズとかなり大きなサイズを表しているだけです。
は
size
パラメータは本当に 2 つの定数を渡すだけのものなのでしょうか?与えられたサイズに対して適切な高さを得るという、私が期待する動作を達成する方法はないのでしょうか?
代替の解決策
1) セルの特定の幅を指定する制約を追加すると、正しいレイアウトが実現されます。その制約は、安全な参照を持たないセルのコレクション ビューのサイズに設定される必要があるため、これは悪い解決策です。その制約の値は、セルが構成されるときに渡すことができますが、それも完全に直感に反しているようです。また、セルまたはそのコンテンツ ビューに直接制約を追加すると多くの問題が発生するため、これは厄介です。
2) テーブルビューを使用します。テーブル ビューは、セルが固定幅であるため、すぐにこの方法で動作しますが、これは、複数列の固定幅セルを持つiPadレイアウトのような他の状況に対応しません。
どのように解決するのですか?
あなたが求めているのは、UITableViewのようなレイアウトを生成するためにUICollectionViewを使用する方法であるように聞こえます。それが本当に欲しいものであるなら、これを行う正しい方法は、カスタム UICollectionViewLayout サブクラスです (おそらく次のようなもの)。 SBTableLayout ).
一方、本当にデフォルトのUICollectionViewFlowLayoutできれいにできる方法があるのかというと、方法はないと思っています。iOS8のセルフサイズセルを使っても、一筋縄ではいきません。根本的な問題は、おっしゃるとおり、フローレイアウトの機構上、ある次元を固定して別の次元を対応させる方法がないことです。(さらに、たとえそれができたとしても、複数行のラベルをサイズ調整するために2つのレイアウトパスが必要になるため、さらに複雑になってしまいます。これは、自己サイズ化セルが systemLayoutSizeFittingSize への 1 回の呼び出しによってすべてのサイズ設定を計算したい方法と一致しないかもしれません)。
しかし、もしあなたがまだ、フローレイアウトでテーブルビューのようなレイアウトを作成したいのであれば、セルがそれ自身のサイズを決定し、コレクションビューの幅に自然に反応するように、もちろんそれは可能です。まだ、面倒な方法があります。私はそれを、"sizing cell"、つまり、コントローラがセルサイズの計算のためだけに保持する非表示の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
}
(このコードは、WWDC2014のセッション「"Advanced Collection View"」のAppleのサンプルコードから引用しています)。
いくつか気になる点があります。ラベルの幅を計算して設定するために、layoutIfNeeded() を使用してセル全体のレイアウトを強制しています。しかし、それだけでは十分ではありません。私は
preferredMaxLayoutWidth
を設定して、ラベルが自動レイアウトでその幅を使用するようにする必要があると思います。そして、そのときだけ
systemLayoutSizeFittingSize
を使って、ラベルを考慮に入れながらセルの高さを計算させることができます。
この方法は好きですか?いいえ! あまりにも複雑に感じますし、レイアウトを 2 回実行することになります。しかし、パフォーマンスが問題にならない限り、コードで 2 回レイアウトを定義するよりも、実行時に 2 回レイアウトを実行する方が良いと思います。
私の希望は、最終的にセルフサイズのセルが異なる方法で動作し、このすべてがよりシンプルになることです。
プロジェクト例 を表示します。
しかし、なぜ自己サイズセルを使用しないのでしょうか?
理論的には、iOS8 の新しい機能である "self-sizing cells" を使えば、このようなことは不要になるはずです。自動レイアウト (AL) を使用してセルを定義した場合、コレクション ビューは十分に賢く、セルのサイズとレイアウトを正しく設定できるはずです。実際には、複数行のラベルでこれを実現した例を見たことがありません。私が思うに この は、セルフサイズ・セルのメカニズムがまだバグがあるためだと思います。
しかし、私は、UILabels が基本的に 2 段階のレイアウト プロセスを必要とするという、Auto Layout とラベルの通常のトリックのためであると確信しています。セルフ サイズのセルで両方のステップを実行する方法は、私には明らかではありません。
そして、私が言ったように、これは本当に別のレイアウトのための仕事です。幅を固定して高さを選択させるのではなく、サイズを持っているものを配置するというのは、フロー レイアウトの本質の一部です。
そして preferredLayoutAttributesFittingAttributes についてはどうでしょう。?
は
preferredLayoutAttributesFittingAttributes:
メソッドは赤信号だと思います。あれはあくまで新しいセルフサイジングセルの機構で使うためにあるのです。だから、そのメカニズムが信頼できない以上、これは答えにはならない。
そして、systemlayoutSizeFittingSize: はどうなっているのでしょうか?
確かにdocsは分かりにくいですね。
のドキュメントは
systemLayoutSizeFittingSize:
と
systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
を渡すだけでよいことを示唆しています。
UILayoutFittingCompressedSize
と
UILayoutFittingExpandedSize
として
targetSize
. しかし、メソッドのシグネチャ自体、ヘッダコメント、関数の動作から、正確には
targetSize
パラメーターの正確な値に応答していることを示しています。
実際、もしあなたが
UICollectionViewFlowLayoutDelegate.estimatedItemSize
を設定すると、新しいセルフ サイズのセル メカニズムを有効にするために、その値は targetSize として渡されるようです。また
UILabel.systemLayoutSizeFittingSize
と全く同じ値が返されるようです。
UILabel.sizeThatFits
. の引数が同じであることを考えると、これは疑わしい。
systemLayoutSizeFittingSize
の引数は大まかな目標であることが想定されており
sizeThatFits:
の引数は最大外接サイズであると思われます。
その他のリソース
このような日常的な要件が研究リソースを必要とすると考えるのは悲しいことですが、私はそう考えています。良い例と議論があります。
- http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
- http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
- WWDC2014 セッション 232, "Advanced User Interfaces with Collection Views" 用のコードです。
関連
-
JenkinsがIOSを自動パッケージングしてモミを配布
-
[解決済み] iOS 8 UITableViewのセパレータインセット0が機能しない件
-
[解決済み] ビューを非表示にしたときに、オートレイアウトで他のビューを移動するには?
-
[解決済み】UIScrollView Scrollable Content Size Ambiguity
-
[解決済み】Xcode 6でAutoLayout制約を使用してアスペクトフィットの動作をエミュレートする
-
[解決済み】UICollectionView 自動レイアウトでセルサイズを変更する方法
-
[解決済み] iOSです。オートレイアウトで複数行のUILabelを使用する
-
[解決済み] UICollectionView、全幅のセル、オートレイアウトの動的な高さを許可しますか?
-
[解決済み] テーブルビューのセクションヘッダーの高さを、オートレイアウトで動的に取得することは可能でしょうか?
-
[解決済み] UICollectionViewを画面ではなくセルでページングする
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] アトミック属性と非アトミック属性の違いは何ですか?
-
[解決済み] performSelectorのセレクタが不明なため、リークが発生する可能性があります。
-
[解決済み] iOSのバージョンを確認する方法を教えてください。
-
[解決済み] 奇妙な不要なXcodeログを隠す
-
[解決済み] Xcode 12、iOS Simulator用にビルドしても、iOS用にビルドされたオブジェクトファイルでは、アーキテクチャ「arm64」用にリンクされます。
-
[解決済み] NSの接頭辞はどういう意味ですか?
-
[解決済み] テキストフィールドを移動する方法(次へボタン/完了ボタン)
-
[解決済み] NSOperationとGrand Central Dispatchの比較
-
[解決済み] 16進カラーバリューの使用方法
-
[解決済み] ぼかしの入ったオーバーレイビューの作成