web-dev-qa-db-ja.com

自己サイズ変更UITableViewCell内の複数のUILabels

私が作成しているこのiOS 8アプリには、TableViewがあり、それらを自己サイズ変更する必要があります。自動レイアウトを使用して実装しましたが、動作します。ほぼ。現在の外観は次のとおりです。

enter image description here

セル内には3つのラベルがあります。 lorem ipsumテキストを含むメインラベル。数字の文字列を含むサブタイトル(これらは2つの別個のラベルです。同じ色を持っているため、混乱する可能性があります。)次に、小さな黒いテキストを含む3番目のラベル。

最初のラベルは問題なく正しくサイズ変更され、それに応じて2番目のラベルが上下に移動します。しかし、問題は3番目の小さなラベルにあります。ご覧のとおり、すべてのテキストに合わせてサイズ変更されていません。

今、奇妙なことが起こっています。私はそれを横向きにし、ここにあります。

enter image description here

スペースがあるため、ラベルは想定されているテキスト全体を表示しています。いいよ次に、ポートレートに戻します。

enter image description here

これで、小さなラベルはすべてのテキストに合わせてサイズが変更されましたが、セルの境界をオーバーフローしています。セルを大きくしようとしましたが、うまくいきませんでした。これはセルのサイズを自動調整するため、それが正しい方法だとは思いません。

自動レイアウトの制約に関するエラーも警告も表示されません。

enter image description here

これらの2行のコードをviewDidLoad()メソッドに設定しました。

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

誰かが私がここで間違っているかもしれないことを教えてもらえますか?

画像を見るだけでは答えが難しく、上記のスニペットの横に投稿するコードがもうないので、問題を示す実行可能なXcodeプロジェクト here をアップロードしました。 (2つのカスタムセルがあります。基本的には同じセルで、2番目のセルでは高さが増加します。)

私は自動レイアウトの制約をいじっていましたが、これが機能していないようです。任意の助けをいただければ幸いです。

ありがとうございました。


UPDATE:

これの助けを借りて チュートリアル 私はいくつかの有用なポインタを見つけました。それによると、各サブビューには、そのすべての側面を固定する制約が必要であり、セルの高さを計算する自動レイアウトを支援する上から下に行く制約があるはずです。私の元の投稿では、各ラベルの間に垂直方向のスペースがあったため、自動レイアウトが適切な高さを計算できなかったのはそのためだと思います。

そこで、いくつかの変更を加えました。

  • ラベル間の垂直方向のスペースを0に減らし、上部と中間のラベルと中間と下部のラベルの間に垂直方向のスペースの制約を設定しました。
  • トップラベルに先頭、末尾、末尾の制約を追加しました。
  • ミドルラベルの先頭と末尾。
  • 先頭、下、最後のラベルの末尾。

さて、ここにもう一つの奇妙な部分があります。最初に実行したとき、一番下のラベルのトリミングの問題はまだあります。

enter image description here

しかし、デバイスを横向きに回転してから縦向きに戻すと、すべてのセルが両方のラベルに合うように適切にサイズ変更されます!

enter image description here

それでも、なぜこれが最初に起こらないのかはまだわかりません。更新されたXcodeプロジェクトは here です。

60
Isuru

ここでの問題は、複数行のラベルのpreferredMaxLayoutWidthプロパティにあります。これは、Wordで折り返すタイミングをラベルに通知するプロパティです。各ラベルのintrinsicContentSizeが正しい高さになるように正しく設定する必要があります。これは、最終的に自動レイアウトがセルの高さを決定するために使用するものです。

Xcode 6 Interface Builderは、このプロパティをAutomaticに設定する新しいオプションを導入しました。残念ながら、nibまたはStoryboardからセルをロードするときにこれが正しく/自動的に設定されない重大なバグがいくつかあります(Xcode 6.2/iOS 8.2の時点)。

このバグを回避するには、preferredMaxLayoutWidthをテーブルビューに表示されたラベルの最終幅と正確に等しくなるように設定する必要があります。実質的に、tableView:cellForRowAtIndexPath:からセルを返す前に次のことを行います。

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

このコードだけを追加しても機能しないのは、これらの3行のコードがtableView:cellForRowAtIndexPath:で実行されるときに、各ラベルの幅を使用してpreferredMaxLayoutWidthを設定しているためです。ただし、この時点でラベルの幅を確認すると、ラベルの幅は、セルが表示され、そのサブビューがレイアウトされた後のラベルの幅とはまったく異なります。

この時点でラベルの幅を正確に取得して、最終的な幅を反映させるにはどうすればよいですか?すべてをまとめたコードを次に示します。

// Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell

cell.bounds = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999)
cell.contentView.bounds = cell.bounds
cell.layoutIfNeeded()

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

では、ここで何をしているのでしょうか?さて、3行の新しいコードが追加されていることに気付くでしょう。まず、テーブルビューの実際の幅と一致するように、このテーブルビューセルの幅を設定する必要があります(これは、テーブルビューが既にレイアウトされ、最終的な幅を持っていることを前提としています)。 Tableビューが最終的にこれを行うため、セル幅を早期に正しくするだけです。

また、高さに99999を使用していることに気付くでしょう。どういうこと?これは、詳細に説明されている問題の簡単な回避策です here 、ここで、制約がセルのcontentViewの現在の高さよりも高い垂直スペースを必要とする場合、実際には実際のことを示さない制約例外を取得問題。セルまたはそのサブビューの高さは、実際にはこの時点では重要ではありません。各ラベルの最終的な幅を取得することだけが重要だからです。

次に、contentViewの境界をセルの境界に等しく設定することにより、セルのcontentViewがセル自体に割り当てたサイズと同じサイズになるようにします。これが必要なのは、作成したすべての自動レイアウト制約がcontentViewに関連しているため、それらを正しく解決するためにはcontentViewが正しいサイズである必要があるためです。セルのサイズを手動で設定するだけで、notが自動的に一致するようにcontentViewのサイズを調整します。

最後に、セルのレイアウトパスを強制します。これにより、自動レイアウトエンジンが制約を解決し、すべてのサブビューのフレームを更新します。セルとcontentViewの幅がテーブルビューでの実行時に同じになるため、ラベルの幅も正しくなります。つまり、各ラベルに設定されたpreferredMaxLayoutWidthが正確になり、ラベルが適切なタイミングで折り返します。これはもちろん、セルがテーブルビューで使用されるときにラベルの高さが正しく設定されることを意味します。

これは間違いなくUIKitのAppleバグです。今のところ回避する必要があります(そのため file bug reports with Apple so彼らは修正を優先します!)。

最後の注意点:テーブルビューセルのcontentView幅がテーブルビューの幅全体を拡張していない場合、たとえばセクションインデックスが右側に表示されている場合、この回避策は問題になります。この場合、セルの幅を設定するときにこれを手動で考慮することを確認する必要があります。これらの値を次のようにハードコードする必要がある場合があります。

let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth
cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999)
83
smileyborg

私はあなたと同じ問題に出会い、それを解決する簡単な解決策を見つけました。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     // dequeue cell...

     // do autolayout staffs...or if the autolayout rule has been set in xib, do nothing

     [cell layoutIfNeeded];

     return cell;
}

そして、自己サイジングはうまく機能しました。私のコードでは、2つのラベルを垂直に配置しました。どちらも動的な高さです。セルの高さは、2つのラベルを含むように正しく設定されています。

50
fogisland

他の人が示唆しているように、制約にエラーがないと仮定すると、この問題は、UITableViewCellAccessoryと組み合わせて複数の行を許可するUILabelを使用することに起因するようです。 iOSがセルをレイアウトし、高さを決定するとき、このアクセサリが原因で発生する幅のオフセットの変化は考慮されず、予期しない場所で切り捨てられます。

UILabelでコンテンツビューの幅全体を拡張したい場合、すべてのフォントサイズでこれを修正するメソッドを作成しました。

-(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell {
    float offset = 0;
    switch ([cell accessoryType]) {
        case UITableViewCellAccessoryCheckmark:
            offset = 39.0;
            break;
        case UITableViewCellAccessoryDetailButton:
            offset = 47.0;
            break;
        case UITableViewCellAccessoryDetailDisclosureButton:
            offset = 67.0;
            break;
        case UITableViewCellAccessoryDisclosureIndicator:
            offset = 33.0;
            break;
        case UITableViewCellAccessoryNone:
            offset = 0;
            break;
    }
    [label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8];
}

これをcellForRowAtIndexPathに入れるだけです

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    #Setup the cell
    ...
    // Fix layout with accessory view
    [self fixWidth:[cell label] forCell:cell];
    return cell;
}

幅を適切に調整し、適切な高さを再計算するために複数の行を作成するラベルに対してこれを行います。これは、動的なフォントサイズでも機能します。

Smileyborgが言ったように、contentViewの幅全体を使用していない場合は、制約を参照し、幅から制約を差し引くこともできます。

編集:以前はセルで「layoutIfNeeded」を実行していましたが、これはパフォーマンスの問題を引き起こしており、とにかく必要ではないようでした。削除しても問題はありません。

6
user2898617

ここには2つの問題があります。

1 /現在、Visual Format Languageを使用して、セルの垂直方向の制約を次のように変換できます。

_Cell: "V:|-(10)-[nameLabel]-(67)-|"
_

次に、2番目の制約グループを設定します。

_Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]"
_

これら2つのグループの制約はうまく混合できず、2番目の問題とのあいまいさが明らかになります。

2 /何らかの理由で、actionsLabelはアプリの起動時に1行に制限されます。次に、デバイスを横向きモードに回転させると、actionsLabelは2行以上で表示されることを受け入れます。その後、デバイスを回転してポートレートモードに戻すと、actionsLabelは2行以上表示し続けます。ただし、actionsLabelは実際にはセルの高さの制約の一部ではないため、セルの境界と重なります。

これらの問題をすべて解決する場合は、最初にxibファイルを最初から再構築することをお勧めします。これにより、actionsLabelの奇妙な動作(デバイスを回転させた場合のみ2行以上)が治ります。

次に、次のように制約を定義する必要があります。

_Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|"
_

もちろん、ラベルには_(>=21)_以外の最小高さ制約を定義できます。同様に、下マージンを-(10)-以外の値に設定できます。

補遺

あなたの質問に答えるために、.xibファイルに以前の制約パターンを持つ単純なプロジェクトを作成しました。次の図は、独自の制約を作成するのに役立ちます。

enter image description here

4
Imanou Petit

「fogisland」の非常に簡単でエレガントな外観のソリューションを試しましたが、うまくいきませんでした。幸いなことに、1行追加するだけですべての方向に機能することがわかりました。新しいレイアウト(layoutIfNeeded)を提案するだけでなく、明示的に要求すること(setNeedLayout)をシステムに伝えるだけです。

    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    return cell
1
Hardy_Germany