42

この質問が何度も聞かれるのを見てきましたが、驚くべきことに、一貫した答えを見たことがないので、自分で試してみます。

実行時に高さを決定する必要がある UITextViews と UILabels を含む独自のカスタム UITableViewCells を含むテーブルビューがある場合、heightForRowAtIndexPath の各行の高さをどのように決定するのですか?

最も明白な最初のアイデアは、cellForRowAtIndexPath 内のセル内の各ビューの高さを計算して合計し、後で取得できるように最終的な高さの合計を保存して、各セルの高さを計算することです。

ただし、cellForRowAtIndexPath は heightForRowAtIndexPath の後に呼び出されるため、これは機能しません。

私が考えることができる唯一のことは、viewDidLoad内ですべての計算を行い、次にすべてのUITableViewCellを作成し、セルの高さを計算してUITableViewCellサブクラス内のカスタムフィールドに保存し、各セルをindexPathとしてNSMutableDictionaryに配置することです次に、cellForRowAtIndexPath および heightForRowAtIndexPath 内の indexPath を使用してディクショナリからセルを取得し、カスタムの高さの値またはセル オブジェクト自体を返します。

ただし、このアプローチは dequeueReusableCellWithIdentifier を使用しないため、間違っているように見えます。代わりに、すべてのセルをコントローラーの辞書に一度にロードし、デリゲート メソッドは辞書から正しいセルを取得するだけです。

私はそれを行う他の方法を見ません。これは悪い考えですか? もしそうなら、これを行う正しい方法は何ですか?

4

10 に答える 10

61

Apple が UITableView を実装する方法は、誰にとっても直感的ではなく、 の役割を誤解しがちですheightForRowAtIndexPath:。一般的な意図は、これが、テーブル内のすべての行に対して非常に頻繁に呼び出すことができる、高速でメモリ負荷の少ないメソッドであることです。これは対照的に、cellForRowAtIndexPath:多くの場合遅く、より多くのメモリを消費しますが、特定の時点で実際に表示する必要がある行に対してのみ呼び出されます。

なぜAppleはこのように実装するのですか? その理由の 1つは、ほとんどの場合、行の高さを計算する方が、セル全体を作成してデータを入力するよりも安価である (または、正しくコーディングすれば安くなる可能性がある) ことです。多くのテーブルですべてのセルの高さが同じであることを考えると、多くの場合、大幅に安くなります。もう 1 つの理由は、iOS がテーブル全体のサイズを知る必要があるためです。これにより、スクロール バーを作成し、スクロール ビューなどに設定することができます。

したがって、すべてのセルの高さが同じでない限り、UITableView が作成され、reloadData メッセージを送信するたびに、データソースにはセルごとに 1 つの heightForRowAtIndexPath メッセージが送信されます。したがって、テーブルに 30 個のセルがある場合、そのメッセージは 30 回送信されます。これらの 30 個のセルのうち 6 個だけが画面に表示されているとします。その場合、作成時に reloadData メッセージを送信すると、UITableView は表示される行ごとに 1 つの cellForRowAtIndexPath メッセージを送信します。つまり、そのメッセージは 6 回送信されます。

ビュー自体を作成せずにセルの高さを計算する方法に戸惑う人もいます。しかし、通常、これは簡単に行うことができます。

たとえば、保持するテキストの量が異なるために行の高さが異なる場合はsizeWithFont:、関連する文字列に対してメソッドの 1 つを使用して計算を行うことができます。これは、ビューを構築してから結果を測定するよりも高速です。セルの高さを変更する場合は、テーブル全体をリロードする必要があることに注意してください (reloadData を使用 - これはデリゲートにすべての高さを要求しますが、表示されているセルのみを要求します)、またはサイズが指定されている行を選択的にリロードする必要があります。変更されました(前回チェックしたときも、heightForRowAtIndexPath:行ごとに呼び出します、ある程度のスクロール作業も行います)。

この質問と、おそらくこれも参照してください。

于 2011-09-09T15:03:45.967 に答える
10

したがって、一度にセルを作成する必要なく、これを行うことができると思います (これは、無駄が多く、多数のセルに対してはおそらく非現実的です)。

UIKit は NSString にいくつかのメソッドを追加します。それらは主要な NSString ドキュメントの一部ではないため、見落としている可能性があります。興味のあるものから始めます:

- (CGSize)sizeWithFont...

Appleドキュメントへのリンクは次のとおりです。

理論的には、これらの NSString の追加はまさにこの問題のために存在します:ビュー自体をロードする必要なしに、テキストのブロックが占めるサイズを把握するためです。おそらく、テーブル ビュー データソースの一部として、各セルのテキストに既にアクセスできます。

UITextView で書式設定を行っている場合、このソリューションによって走行距離が異なる可能性があるため、「理論的に」と言います。しかし、私はそれがあなたをそこに少なくとも途中まで連れて行ってくれることを願っています. Cocoa is My Girlfriendにこの例があります。

于 2011-01-28T01:00:32.953 に答える
5

私が過去に使用したアプローチは、テーブルで使用するセルの単一のインスタンスを保持するクラス変数を作成することです(私はそれをプロトタイプセルと呼びます)。次に、カスタムセルクラスに、データを入力してセルに必要な高さを決定するメソッドがあります。データを実際に入力する方法のより単純な変形である可能性があることに注意してください。たとえば、セル内のUILabelのサイズを実際に変更する代わりに、NSString heightメソッドを使用して、最終セル内のUILabelの高さを決定できます。次に、セルの合計の高さ(および下部の境界線)とUILabelの配置を使用して、実際の高さを決定します。プロトタイプセルを使用して、要素が配置されている場所を把握し、ラベルの高さが44単位になる場合の意味を把握します。

次に、そのheightForRow:メソッドを呼び出して高さを返します。

私はcellForRow:実際にラベルにデータを入力してサイズを変更するメソッドを使用します(UITableViewセルのサイズを自分で変更することはありません)。

凝ったものにしたい場合は、渡したデータに基づいて各セルの高さをキャッシュすることもできます(たとえば、高さを決定するのがそれだけの場合は、1つのNSStringにある可能性があります)。多くの場合同じであるデータがたくさんある場合は、メモリ内だけでなく永続的なキャッシュを使用するのが理にかなっている場合があります。

文字数や単語数に基づいて行数を推定することもできますが、私の経験ではうまくいきません。問題が発生すると、通常、セルとその下のすべてのセルが台無しになります。

于 2011-01-28T01:51:45.663 に答える
5

これは、UTextView 内のテキストの量に基づいてセルの高さを計算する方法です。

#define PADDING  21.0f

- (CGFloat)tableView:(UITableView *)t heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if(indexPath.section == 0 && indexPath.row == 0)
    {   
        NSString *practiceText = [practiceItem objectForKey:@"Practice"];
        CGSize practiceSize = [practiceText sizeWithFont:[UIFont systemFontOfSize:14.0f] 
                   constrainedToSize:CGSizeMake(tblPractice.frame.size.width - PADDING * 3, 1000.0f)];
        return practiceSize.height + PADDING * 3;
    }

    return 72;
}

もちろん、ニーズに合わせて およびその他の変数を調整する必要がありますが、これにより、指定されたテキストの量に基づいて、PADDINGが含まれるセルの高さが設定されます。UITextViewしたがって、テキストが 3 行しかない場合、セルはかなり短く、テキストが 14 行ある場合、セルの高さはかなり大きくなります。

于 2011-09-08T07:37:24.183 に答える
3

各セルの計算を tableView:heightForRowAtIndexPath: に移動する際の問題は、 reloadData が呼び出されるたびにすべてのセルが再計算されることです。少なくとも数百行ある可能性がある私のアプリケーションでは、遅すぎます。デフォルトの行の高さを使用し、計算時に行の高さをキャッシュする代替ソリューションを次に示します。高さが変更されるか、最初に計算されると、新しい高さをテーブル ビューに通知するためにテーブルのリロードがスケジュールされます。これは、行の高さが変化したときに行が 2 回表示されることを意味しますが、比較するとわずかです。

@interface MyTableViewController : UITableViewController {
    NSMutableDictionary *heightForRowCache;
    BOOL reloadRequested;
    NSInteger maxElementBottom;
    NSInteger minElementTop;
}

tableView:heightForRowAtIndexPath:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // If we've calculated the height for this cell before, get it from the height cache.  If
    // not, return a default height.  The actual size will be calculated by cellForRowAtIndexPath
    // when it is called.  Do not set too low a default or UITableViewController will request
    // too many cells (with cellForRowAtIndexPath).  Too high a value will cause reloadData to
    // be called more times than needed (as more rows become visible).  The best value is an
    // average of real cell sizes.
    NSNumber *height = [heightForRowCache objectForKey:[NSNumber numberWithInt:indexPath.row]];
    if (height != nil) {
        return height.floatValue;
    }

    return 200.0;
}

tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Get a reusable cell
    UITableViewCell *currentCell = [tableView dequeueReusableCellWithIdentifier:_filter.templateName];
    if (currentCell == nil) {
        currentCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_filter.templateName];
    }

    // Configure the cell
    // +++ unlisted method sets maxElementBottom & minElementTop +++
    [self configureCellElementLayout:currentCell withIndexPath:indexPath];

    // Calculate the new cell height
    NSNumber *newHeight = [NSNumber numberWithInt:maxElementBottom - minElementTop];

    // When the height of a cell changes (or is calculated for the first time) add a
    // reloadData request to the event queue.  This will cause heightForRowAtIndexPath
    // to be called again and inform the table of the new heights (after this refresh
    // cycle is complete since it's already been called for the current one).  (Calling
    // reloadData directly can work, but causes a reload for each new height)
    NSNumber *key = [NSNumber numberWithInt:indexPath.row];
    NSNumber *oldHeight = [heightForRowCache objectForKey:key];
    if (oldHeight == nil || newHeight.intValue != oldHeight.intValue) {
        if (!reloadRequested) {
            [self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0];
            reloadRequested = TRUE;
        }
    }

    // Save the new height in the cache
    [heightForRowCache setObject:newHeight forKey:key];

    NSLog(@"cellForRow: %@ height=%@ >> %@", indexPath, oldHeight, newHeight);

    return currentCell;
}
于 2012-02-07T00:41:27.093 に答える
3

私が見たこれの最良の実装は、Three20 TTTableView クラスの方法です。

heightForRowAtIndexPath:基本的に、メソッドを TTTableCell クラスのクラス メソッドにデリゲートする UITableViewController から派生したクラスがあります。

次に、そのクラスは常に、draw メソッドで行うのと同じ種類のレイアウト計算を行うことによって、正しい高さを返します。これをクラスに移動することで、セル インスタンスに依存するコードを記述する必要がなくなります。

他に選択肢はありません。パフォーマンス上の理由から、フレームワークはセルの高さを要求する前にセルを作成しません。また、多くの行が存在する可能性がある場合は、セルを作成したくありません。

于 2011-09-07T20:06:55.797 に答える
2

本当に良い質問です。これについてもより多くの洞察を探しています。

問題の明確化:

  1. 行の高さは (cellForRowAtIndexPath) の前に呼び出されます
  2. ほとんどの人は、CELL (cellForRowAtIndexPath) 内の高さタイプの情報を計算します。

いくつかのソリューションは驚くほどシンプルで効果的です。

  • 解決策 1: heightForRowAtIndexPath に強制的にセルの仕様を計算させます。Massimo Cafaro 9月9日

  • 解決策2:セルの最初のパス「標準サイズ」を実行し、セルの高さがある場合は結果をキャッシュしてから、新しい高さを使用してテーブルをリロードします-対称

  • 解決策 3: 他の興味深い答えは、three20 が関係しているようですが、答えに基づいて、この「問題」を解決しやすくするストーリーボード/xib に描かれたセルがないようです。

于 2012-12-04T04:59:18.377 に答える
0

私は最初に提案したアイデアを採用しました。これは正常に機能しているように見えます。これにより、すべてのカスタムセルを事前にviewDidLoadにロードし、インデックスをキーとしてNSMutableDictionaryに格納します。私は関連するコードを投稿しており、このアプローチについて誰かが持っている批評や意見があればいいのですが。具体的には、viewDidLoadのnibからUITableViewCellsを作成する方法にメモリリークの問題があるかどうかはわかりません。これは、リリースしないためです。

@interface RecentController : UIViewController <UITableViewDelegate, UITableViewDataSource> {

NSArray *listData;
NSMutableDictionary *cellBank;

}

@property (nonatomic, retain) NSArray *listData;
@property (nonatomic, retain) NSMutableDictionary *cellBank;
@end



@implementation RecentController

@synthesize listData;
@synthesize cellBank;

---

- (void)viewDidLoad {

---

self.cellBank = [[NSMutableDictionary alloc] init];

---

//create question objects…

--- 

NSArray *array = [[NSArray alloc] initWithObjects:question1,question2,question3, nil];

self.listData = array;

//Pre load all table row cells
int count = 0;
for (id question in self.listData) {

    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QuestionHeaderCell" 
                                                 owner:self 
                                               options:nil];
    QuestionHeaderCell *cell;

    for (id oneObject in nib) {
        if([oneObject isKindOfClass:[QuestionHeaderCell class]])
            cell = (QuestionHeaderCell *) oneObject;

            NSNumber *key = [NSNumber numberWithInt:count];
            [cellBank setObject:[QuestionHeaderCell makeCell:cell 
                                                  fromObject:question] 
                         forKey:key];
            count++;

    }
}

[array release];
[super viewDidLoad];
}



#pragma mark -
#pragma mark Table View Data Source Methods

-(NSInteger) tableView: (UITableView *) tableView
numberOfRowsInSection: (NSInteger) section{

return [self.listData count];

}

-(UITableViewCell *) tableView: (UITableView *) tableView
     cellForRowAtIndexPath: (NSIndexPath *) indexPath{

NSNumber *key = [NSNumber numberWithInt:indexPath.row];
return [cellBank objectForKey:key];


}

-(CGFloat) tableView: (UITableView *) tableView
heightForRowAtIndexPath: (NSIndexPath *) indexPath{

NSNumber *key = [NSNumber numberWithInt:indexPath.row];
return [[cellBank objectForKey:key] totalCellHeight];

}

@end



@interface QuestionHeaderCell : UITableViewCell {

UITextView *title;
UILabel *createdBy;
UILabel *category;
UILabel *questionText;
UILabel *givenBy;
UILabel *date;
int totalCellHeight;

}

@property (nonatomic, retain) IBOutlet UITextView *title;
@property (nonatomic, retain) IBOutlet UILabel *category;
@property (nonatomic, retain) IBOutlet UILabel *questionText;
@property (nonatomic, retain) IBOutlet UILabel *createdBy;
@property (nonatomic, retain) IBOutlet UILabel *givenBy;
@property (nonatomic, retain) IBOutlet UILabel *date;
@property int totalCellHeight;

+(UITableViewCell *) makeCell:(QuestionHeaderCell *) cell 
               fromObject:(Question *) question;

@end



@implementation QuestionHeaderCell
@synthesize title;
@synthesize createdBy;
@synthesize givenBy;
@synthesize questionText;
@synthesize date;
@synthesize category;
@synthesize totalCellHeight;







- (void)dealloc {
[title release];
[createdBy release];
[givenBy release];
[category release];
[date release];
[questionText release];
[super dealloc];
}

+(UITableViewCell *) makeCell:(QuestionHeaderCell *) cell 
                 fromObject:(Question *) question{


NSUInteger currentYpos = 0;

cell.title.text = question.title;

CGRect frame = cell.title.frame;
frame.size.height = cell.title.contentSize.height;
cell.title.frame = frame;
currentYpos += cell.title.frame.size.height + 2;


NSMutableString *tempString = [[NSMutableString alloc] initWithString:question.categoryName];
[tempString appendString:@"/"];
[tempString appendString:question.subCategoryName];

cell.category.text = tempString;
frame = cell.category.frame;
frame.origin.y = currentYpos;
cell.category.frame = frame;
currentYpos += cell.category.frame.size.height;

[tempString setString:@"Asked by "];
[tempString appendString:question.username];
cell.createdBy.text = tempString;

frame = cell.createdBy.frame;
frame.origin.y = currentYpos;
cell.createdBy.frame = frame;
currentYpos += cell.createdBy.frame.size.height;


cell.questionText.text = question.text;
frame = cell.questionText.frame;
frame.origin.y = currentYpos;
cell.questionText.frame = frame;
currentYpos += cell.questionText.frame.size.height;


[tempString setString:@"Advice by "];
[tempString appendString:question.lastNexusUsername];
cell.givenBy.text = tempString;
frame = cell.givenBy.frame;
frame.origin.y = currentYpos;
cell.givenBy.frame = frame;
currentYpos += cell.givenBy.frame.size.height;


cell.date.text = [[[MortalDataStore sharedInstance] dateFormat] stringFromDate: question.lastOnDeck];
frame = cell.date.frame;
frame.origin.y = currentYpos-6;
cell.date.frame = frame;
currentYpos += cell.date.frame.size.height;

//Set the total height of cell to be used in heightForRowAtIndexPath
cell.totalCellHeight = currentYpos;

[tempString release];
return cell;

}

@end
于 2011-01-29T03:41:45.207 に答える
0

これは非常に単純なケースで、ラベルに保持されたメモを含むセルです。メモ自体は、課す最大長に制限されているため、複数行の UILabel を使用し、次の例に示すように、各セルの正しい 8 を動的に計算します。ほとんど同じように UITextView を扱うことができます。

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

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

    // Configure the cell...
    Note *note = (Note *) [fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = note.text;
    cell.textLabel.numberOfLines = 0; // no limits

    DateTimeHelper *dateTimeHelper = [DateTimeHelper sharedDateTimeHelper];
    cell.detailTextLabel.text = [dateTimeHelper mediumStringForDate:note.date];

    cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;


    return cell;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

    //NSLog(@"heightForRowAtIndexPath: Section %d Row %d", indexPath.section, indexPath.row);
    UITableViewCell *cell = [self tableView: self.tableView cellForRowAtIndexPath: indexPath];
    NSString *note = cell.textLabel.text;
    UIFont *font = [UIFont fontWithName:@"Helvetica" size:14.0];
    CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);
    CGSize bounds = [note sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
    return (CGFloat) cell.bounds.size.height + bounds.height;

}
于 2011-09-09T10:44:25.053 に答える
0

このトピックについて何度も検索した結果、最終的にこのロジックが思い浮かびました。シンプルなコードですが、十分に効率的ではないかもしれませんが、これまでのところ、私が見つけることができる最高のものです.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSDictionary * Object=[[NSDictionary alloc]init];
  Object=[Rentals objectAtIndex:indexPath.row];
  static NSString *CellIdentifier = @"RentalCell";
  RentalCell *cell = (RentalCell *)[tableView
                                  dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil)
  {
      cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  }
   NSString* temp=[Object objectForKey:@"desc"];
   int lines= (temp.length/51)+1;
   //so maybe here, i count how many characters that fit in one line in this case 51
   CGRect correctSize=CGRectMake(cell.infoLabel.frame.origin.x, cell.infoLabel.frame.origin.y,    cell.infoLabel.frame.size.width, (15*lines));
   //15 (for new line height)
   [cell.infoLabel setFrame:correctSize];
   //manage your cell here
}

ここに残りのコードがあります

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

    NSDictionary * Object=[[NSDictionary alloc]init];
    Object=[Rentals objectAtIndex:indexPath.row];
    static NSString *CellIdentifier = @"RentalCell";
    RentalCell *cells = (RentalCell *)[tableView
                                  dequeueReusableCellWithIdentifier:CellIdentifier];
    NSString* temp=[Object objectForKey:@"desc"];
    int lines= temp.length/51;

    return (CGFloat) cells.bounds.size.height + (13*lines);
}
于 2012-12-11T07:05:20.153 に答える