15

私の質問は基本的に、 UITableCell で UILabel (および他の要素と思われる) の動的な高さをサポートし、回転時にラベルの幅/高さとセルの高さを正しくサイズ変更するための最良の方法に要約されます。

UILabels の予想される高さを取得し、それに応じて高さのサイズを変更する方法は認識していますが、横向きもサポートしている場合はかなり扱いにくいようです。

これはlayoutSubviewsのルートになる可能性があると考えていますが、それをセルの高さを計算する必要があるテーブルとどのように組み合わせるかはわかりません。別の興味深い投稿では、初期化時にフレームを手動で設定して、既知の量から計算を行っていることを確認していますが、それは問題の一部にしか対処していません。

赤い矢印は動的な高さのラベルを指し、青い矢印は高さを変更するセルを指しています。

動的 UILabel/セルの高さ/幅

私はそれを正しく機能させることができましたが、正しい方法かどうかはわかりません。

ここに私が学んだことのいくつかがあります:

  • の cell.frame は、cellForRowAtIndexPath常にポートレート モードでのサイズを示します。(つまり、iPhone アプリの場合、常に 320 と報告されます)。
  • 参照用のプロトタイプ セルをプロパティに保存すると、同じ問題が発生し、常に縦向きモードになります。
  • ラベル幅のマスクの自動サイズ変更は、この使用例では役に立たないようであり、実際には、回転時に計算された高さに問題が発生します。
  • の tableView.frame はcellForRowAtIndexPath、正しい向きでサイズを指定します
  • [tableView reloadData]ローテーションで呼び出す必要があります。セルとラベルの高さを更新する他の方法が見つかりませんでした

これを機能させるために私が取った手順は次のとおりです。

  1. 何らかの理由で適切なラベルの高さを取得するのを妨げるため、UILabels の自動サイズ変更マスクを無効にします。
  2. 各ラベル フォントへviewDidLoadの参照と、縦向きの tableView と比較したラベル幅のパーセンテージのフロートを取得します。

    CWProductDetailDescriptionCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
    self.descriptionLabelWidthPercentage = cell.descriptionLabel.frame.size.width / 320;
    self.descriptionLabelFont = cell.descriptionLabel.font;
    
  3. ではheightForRowAtIndexPath、tableView の幅と既に取得したパーセンテージを使用してセルの高さを計算します。

    case TableSectionDescription:
        CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
        CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:self.descriptionLabelFont];
        return newLabelFrameSize.height + kTextCellPadding;
    
  4. ではcellForRowAtIndexPath、ラベル フレームのフレームを計算して更新します。

    cell = [tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.text = self.product.descriptionText;
    CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
    CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, labelWidth, newLabelFrameSize.height);
    
  5. あなたdidRotateFromInterfaceOrientationがする必要があります[self.tableView reloadData]

この方法の問題点:

  1. IB で設定する可能性のある自動サイズ変更マスクとマージンを活用することはできません
    • それらは、セルを返すまで発火しないようですcellForRowAtIndexPath (これが正確かどうかはわかりません)
    • どういうわけか高さの計算が台無しになり、ラベルが横向きで必要以上に高くなります。
  2. ラベルの幅が方向と方向で同じパーセンテージでない場合は、両方のパーセンテージを知る必要があります
  3. ラベルの幅のパーセンテージを知る/計算する必要があります。理想的には、自動サイズ変更マスクが処理を行った後、これらをオンザフライで計算できるようになります。
  4. 不器用な感じです。インデントのある編集モードでは、さらに頭が痛くなりそうな予感がします。

そう。これを行う正しい方法は何ですか?私は多くの SO の質問を見てきましたが、これを正確に扱っているものはありません。

4

3 に答える 3

12

どうやら、私の巨大な質問を書き留め、それを理解するためにコンピューターから休憩する必要があったようです.

だからここに解決策があります:

その場で UILabel の高さを動的に計算し、マスクの自動サイズ変更を利用する主な手順は次のとおりです。

  1. 高さを調整する必要がある各セルのプロトタイプ セルを保持します。
  2. heightForRowAtIndexPath
    • 現在の向きに合わせて参照セルの幅を調整します
    • 高さを計算するために、含まれているUILabelフレーム幅を確実に使用できます
  3. cellForRowAtIndexPath初期設定以外 のフレーム調整は行わないでください
    • UITableViewCells は、これが呼び出された直後にすべてのレイアウトを実行するため、せいぜいあなたの努力は無駄であり、最悪の場合、物事を台無しにします
  4. でフレーム調整を行いますwillDisplayCell forRowAtIndexPath
    • 私は激怒している私はこれを忘れていました
    • すべての UITableViewCell 内部レイアウトの後、描画の直前に発生します
    • 自動サイズ変更マスクを含むラベルには、計算に使用できる正しい幅が既に設定されています

viewDidLoad で、高さ調整が必要なセルへの参照を取得します

これらは保持/強力 (ARC) であり、テーブル自体には追加されないことに注意してください。

self.descriptionReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
self.attributeReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"AttributeCell"];

可変高セルのin heightForRowAtIndexPathdo 計算

最初に参照セルの幅を更新して、サブビュー (UILabels) をレイアウトし、それを計算に使用できるようにします。次に、更新されたラベル幅を使用してラベルの高さを決定し、必要なセル パディングを追加してセルの高さを計算します。(これらの計算は、セルがラベルと共に高さを変更する場合にのみ必要であることを覚えておいてください)。

UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
float width = UIDeviceOrientationIsPortrait(orientation) ? 320 : 480;

case TableSectionDescription:
    CGRect oldFrame = self.descriptionReferenceCell.frame;
    self.descriptionReferenceCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, width, oldFrame.size.height);
    CGFloat referenceWidth = self.descriptionReferenceCell.descriptionLabel.frame.size.width;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(referenceWidth, MAXFLOAT) AndFont:self.descriptionReferenceCell.descriptionLabel.font];
    return newLabelFrameSize.height + kTextCellPadding;

cellForRowAtIndexPath方向に依存する動的レイアウト関連の更新を行わないでください

ラベルwillDisplayCell forRowAtIndexPathの高さ自体を更新する

表のセルが実行されたlayoutSubviewsので、ラベルには既に正しい幅があり、これを使用してラベルの正確な高さを計算します。ラベルの高さを安全に更新できます。

case TableSectionDescription:
    CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
    CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(oldFrame.size.width, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
    ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newLabelFrameSize.height);
    break;

回転時にデータをリロード

データをリロードしないと、表示されているセルの高さとラベルは更新されません。

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    [self.tableView reloadData];
}

他に指摘すべき唯一のことは、基本的な呼び出しをラップするだけのラベルの高さを計算するためにヘルパー クラスを使用していることです。実際には、コードの多くを保存することはもうありません (私はそこに他のものを持っていました) ので、文字列から直接通常の方法を使用してください。

- (CGSize) sizeForString:(NSString*)string WithConstraint:(CGSize)constraint AndFont:(UIFont *)font {
    CGSize labelSize = [string sizeWithFont:font
                          constrainedToSize:constraint 
                              lineBreakMode:UILineBreakModeWordWrap];
    return labelSize;
}
于 2012-04-20T04:16:22.277 に答える
0

私はあなたの観察のほとんどに同意します。ただし、いくつかの考えがあります。

あなたは言う:

cellForRowAtIndexPath の cell.frame は、常にポートレート モードでのサイズを示します。(つまり、iPhone アプリの場合、常に 320 と報告されます)。

これは私の経験ではありません。私の中で私tableView:cellForRowAtIndexPathはちょうど入れました

NSLog(@"Cell frame.size.width=%f", cell.frame.size.width);

また、方向に応じて 320 または 480 を報告しています。したがって、フレーム計算ロジックを willDisplayCell に移動しても、何のメリットもありません。

あなたは言う:

heightForRowAtIndexPathtableView の幅と既に取得したパーセンテージを使用してセルの高さを計算します。

ええ、あなたはそれを行うことができます。セルの右側の説明が非常に長くなる可能性が高い場合は、セルの左側のラベルを固定幅に保ち、より広い画面を利用して、長い説明を最大限に活用することをお勧めします。セルの右側。ただし、右側に短い説明が頻繁にある場合は、画面幅の目標パーセンテージに基づいて再配置することをお勧めします。個人的には、パーセンテージに基づいてピクセルを変換するのではなく、固定比率を使用することもできます (たとえば、左側のラベルは常に幅の 33% を使用し、右側のラベルは常にセルの 66% を使用し、パーセンテージを単純化する場合があります)。論理的ですが、機能的には同等です)。

あなたは言う:

IB で設定する可能性のある自動サイズ変更マスクとマージンを活用することはできません

ええ、私はそれを試したことがありませんでした(今までとあなたが説明したのと同じ動作が見られます)が、たとえそれが機能したとしても、使用しているワードラッピングに基づいて高さを再計算する必要があるため、悪い考えです、そのため、近づくかもしれませんが、とにかく高さを再計算して、とにかく正しくする必要があります. (率直に言って、「ラベルのフォントと幅とテキストに基づいて高さを自動調整する」と言えたらいいのにと思いますが、それは存在しません。)これをより優雅に処理します。

あなたが言った:

データをリロードしないと、表示されているセルの高さとラベルは更新されません。

次に、呼び出しを提案しreloadDataました。didRotateFromInterfaceOrientationポートレートの UILabels がランドスケープになった直後に点滅するのを見ているので、遅すぎることはありませんが、ここで呼び出されたランドスケープの再描画が行われる前です。UITableViewCell と override をサブクラス化したくありませんdrawRectが、 UILabel フレームを横向きに再描画する前に再レイアウトする方法がわかりません。そして、これを に移動しようとした最初の試みwillRotateToInterfaceOrientationはうまくいきませんでしたが、おそらく私はそこで何かをしなかったのでしょう。

于 2012-04-20T05:19:18.393 に答える
0

ありがとう、これはとても役に立ちました。ただし、幅をハードコーディングしたくありませんでした。私はこの問題をよく見ていて、ここに私が見つけたものがあります。

基本的に、テーブル ビューが独自のレイアウトを行うためにセルの高さを知る必要があるという点で、ニワトリが先か卵が先かという問題があります。決定。

この循環依存を解消する 1 つの方法は、幅を 320 または 480 にハードコーディングすることです。これは、このページの他の場所で提案されています。ただし、サイズをハードコーディングするという考えは好きではありませんでした。

もう 1 つのアイデアは、プロトタイプ セルを使用して事前にすべての高さを計算することです。私はこれを試しましたUITableViewCellが、テーブルビューの実際のスタイルとサイズに合わせて構成されていないセルしか作成しないため、高さの計算が間違っていました。どうやら、特定のテーブル ビューのセルを手動で構成する方法はないようです。あなたはそれをさせUITableViewなければなりません。

私が落ち着いた戦略は、テーブル ビューに通常どおりにすべてを実行させることです。これには、独自のセルの作成と構成が含まれます。次に、すべてのセルが設定されたら、実際の行の高さを計算し、テーブル ビューを更新します。欠点は、テーブル ビューが 2 回読み込まれることです (1 回はデフォルトの高さで、もう 1 回は適切な高さで)。ただし、これはかなりクリーンで堅牢なソリューションになるはずです。


_cellHeightsこのサンプルでは、​​テーブル ビュー セクションごとに計算された行の高さを保持する配列の配列であるインスタンス変数があることを前提としています。

@interface MyViewController () {
    NSArray *_cellHeights; // array of arrays (for each table view section) of numbers (row heights)
}

現在のセルの内容に基づいてすべてのセルの高さを計算するメソッドを作成します。

- (void)_recalculateCellHeights
{
    NSMutableArray *cellHeights = [[NSMutableArray alloc] init];

    for (NSUInteger section=0; section<[self.tableView numberOfSections]; section++) {
        NSMutableArray *sectionCellHeights = [[NSMutableArray alloc] init];

        for (NSUInteger row=0; row<[self.tableView numberOfRowsInSection:section]; row++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

            // Set cell's width to match table view's width
            CGRect cellFrame = cell.frame;
            cellFrame.size.width = CGRectGetWidth(self.tableView.frame);
            cell.frame = cellFrame;

            CGFloat cellHeight = self.tableView.rowHeight; // default

            if (cell.textLabel.numberOfLines == 0 || (cell.detailTextLabel && cell.detailTextLabel.numberOfLines == 0)) {
                [cell layoutIfNeeded]; // this is necessary on iOS 7 to give the cell's text labels non-zero frames

                // Calculate the height of the cell based on the text
                [cell.textLabel sizeToFit];
                [cell.detailTextLabel sizeToFit];

                cellHeight = CGRectGetHeight(cell.textLabel.frame) + CGRectGetHeight(cell.detailTextLabel.frame);

                // This is the best I can come up with for this :(
                if (self.tableView.separatorStyle != UITableViewCellSeparatorStyleNone) {
                    cellHeight += 1.0;
                }
            }

            [sectionCellHeights addObject:[NSNumber numberWithFloat:cellHeight]];
        }

        [cellHeights addObject:sectionCellHeights];
    }

    _cellHeights = cellHeights;
}

では-tableView:cellForRowAtIndexPath:、通常どおりセルを作成し、レイアウトを実行せずに textLabel に入力します (Bob がこのページの他の場所で述べたように: 「UITableViewCells は、これが呼び出された直後にすべてのレイアウトを実行するため、せいぜいあなたの努力は無駄であり、最悪の場合、物事を台無しにする」)。重要なことは、ラベルのnumberOfLinesプロパティを 0 に設定して動的な高さを許可することです。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

        // Assign any fonts or other adjustments which may affect height here

        // Enable multiple line support so textLabel can grow vertically
        cell.textLabel.numberOfLines = 0;
    }

    // Configure the cell...
    cell.textLabel.text = [self.texts objectAtIndex:indexPath.section];

    return cell;
}

-tableView:heightForRowAtIndexPath:、事前に計算されたセルの高さを返します。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (_cellHeights) {
        NSArray *sectionCellHeights = [_cellHeights objectAtIndex:indexPath.section];
        return [[sectionCellHeights objectAtIndex:indexPath.row] floatValue];
    }

    return self.tableView.rowHeight; // default
}

-viewDidAppear:、セルの高さの再計算をトリガーし、テーブルを更新します。この時点では、テーブル ビューのジオメトリ用にセルがまだ構成されていないため、これは時間で-viewDidAppear:はなく、その時点で行う必要があることに注意してください。-viewWillAppear:-viewWillAppear:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self _recalculateCellHeights];
    [self.tableView reloadData];
}

-didRotateFromInterfaceOrientation:、同じことを行います。

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self _recalculateCellHeights];
    [self.tableView reloadData];
}

で特別なことをする必要はありません-tableView:willDisplayCell:forRowAtIndexPath:

于 2013-09-26T20:48:25.203 に答える