不使用contentView frame计算UITableViewCell高度的最佳方法

9

摘要

考虑到我们并不总是知道单元格的框架或其内容视图将会是什么样子(由于编辑、旋转、辅助视图等),当单元格包含可变高度的文本字段或标签时,在tableView:heightForRowAtIndexPath:中计算高度的最佳方法是什么?

我的一个UITableViewController包含以下演示:

UITableViewCell带有UITextView。

UITextView应该与UITableViewCell的宽度和高度相同。

enter image description here

我创建了UITableViewCell子类,然后用UITextView进行初始化(UITextView是我的UITableViewController的私有字段)。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 
     static NSString *CellIdentifier = @"TextViewCell";
     UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[BTExpandableTextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier textView:_notesTextView] autorelease];
     }    
     return cell;
}

我在我的UITableViewCell子类中实现了以下方法:

- (void)layoutSubviews{
    [super layoutSubviews];
    CGFloat height = [textView.text sizeWithFont:textView.font constrainedToSize:CGSizeMake(textView.frame.size.width, MAXFLOAT)].height + textView.font.lineHeight;
    textView.frame = CGRectMake(0, 0, self.contentView.frame.size.width, (height < textView.font.lineHeight * 4) ? textView.font.lineHeight * 4 : height);    
    [self.contentView addSubview:textView];
}

当然,我实现了以下的UITableViewDataSource方法(看!我使用self.view.frame.size.width(但实际上我需要UITableViewCell contentView frame的宽度):

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

    CGFloat height  = [_notesTextView.text sizeWithFont:_notesTextView.font 
                                      constrainedToSize:CGSizeMake(self.view.frame.size.width, MAXFLOAT)].height;        
    CGFloat groupedCellCap = 20.0;     
    height          += groupedCellCap; 

    if(height < [BTExpandableTextViewCell minimumTextViewHeightWithFont:_notesTextView.font]){
        height = [BTExpandableTextViewCell minimumTextViewHeightWithFont:_notesTextView.font];
    }        
    return height;
}    

我还实现了以下方法(虽然不是那么重要,但我还是会发布它,只是为了解释一下单元格的高度是动态的,当更改UITextView中的文本后,它会收缩或展开)

 - (void)textViewDidChange:(UITextView *)textView{
    CGFloat height = [_notesTextView.text sizeWithFont:_notesTextView.font 
                                 constrainedToSize:CGSizeMake(_notesTextView.frame.size.width, MAXFLOAT)].height;
    if(height > _notesTextView.frame.size.height){
        [self.tableView beginUpdates];
        [self.tableView endUpdates];
    }
}

现在,我的问题是: 加载视图后,UITableViewController会按以下顺序调用方法:(为简化起见,我将删除一些,如titleForHeaderInSection等)

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{ 

并且仅限于这样
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

看,我应该在cellForRowAtIndexPath之前返回正确的UITableViewCell高度!这意味着:我不知道UITableViewCell contentView的框架。并且我不能通过编程方式获取它。
这个宽度可以是以下之一:
1. iPhone普通表格,纵向方向 2. iPhone普通表格,横向方向 3. iPhone分组表格,纵向方向 4. iPhone分组表格,横向方向 5. iPad也是如此(另外4个值)
而且不要忘记,由于UITableViewCell accessoryType或UITableView编辑状态,contentView frame可能会更小。(例如,如果我们有带有任何高度的多行UILabel和任何accessoryView的UITableViewCell,并且处于任何编辑状态)。
所以这个问题是根本性的:我只是无法获得用于限制的单元格contentView框架宽度,因为我应该在单元格布局contentView之前返回此高度。(顺便说一下,这很合理)但是这个contentView框架真的很重要。
当然,有时我可以确切地知道这个宽度并“硬编码”它(例如:UITableViewCellAccessoryDisclosureIndicator的宽度为20像素,tableView不能处于编辑状态,那么我可以写self.view.frame.size.width - 20,任务就完成了)!或者有时contentView等于UITableViewController的视图框架!
有时我在-tableView: heightForRowAtIndexPath: 方法中使用self.view.frame.width。(像现在这样,它工作得很好,但是由于分组UITableView的缘故,不完美应该减去一些常数值,它们对于2个设备* 2个方向是不同的)。
有时我在UITableViewCell中使用一些#defined常量(如果我确切地知道宽度)...
有时我使用一些虚拟预分配的UITableViewCell(这只是愚蠢的,但有时非常优雅且易于使用)...
但我不喜欢其中任何一个。
最好的决定是什么? 也许我应该创建一些帮助类,该类将使用以下参数进行初始化: accessory views、设备方向、设备类型、表视图编辑状态、表视图样式(普通、分组)、控制器视图框架和其他一些包括一些常量的内容(例如分组tableView偏移量等),并用它来查找预期的UITableViewCell contentView宽度? ;)
谢谢

你并不决定表视图数据源和委托方法的调用顺序,这是由数据源和委托UITableView类完成的,因此它们在代码中出现的位置并不重要,Apple会根据需要自行调用它们... - Ladislav
我知道 ;) 无论如何,在heightForRowAtIndexPath中为宽度检索单元格根本不好,因为控制器显然在获取高度后布局单元格。这是非常合理的。与此无关。我们不应该试图干扰 :) - Valentyn Kuznietsov
所以基本上你只想捕捉每次单元格改变宽度并相应地调整UITextView? - Justin Paulson
这是关于在heightForRowAtIndexPath中检索正确的UITableViewCell contentView宽度以限制高度的问题。 "Cell changes width"也与此有关 - 例如,在更改设备方向后,单元格会更改宽度。我不想硬编码一些常量。就像在iPad上,它是self.view.frame.size.width - 20; 在iPhone上,它是self.view.frame.size.width - 10; 如果表处于编辑状态,则另外减去“-20”...等等。 - Valentyn Kuznietsov
2
我认为这是一个很好的问题,虽然有点长 - 我在顶部添加了一个摘要,希望你不介意,也希望我已经把你的问题主要点概括出来了。如果你不喜欢,随时可以重新编辑/回滚。 - jrturton
2个回答

1

表视图使用tableView:heightForRowAtIndexPath:方法在创建任何UITableViewCell单元格之前确定其contentSize。如果你停下来想一想,这是有道理的,因为你使用UIScrollView的第一件事就是设置它的contentSize。我以前遇到过类似的问题,我发现最好有一个辅助函数,可以获取进入UITableViewCell的内容并预测该UITableViewCell的高度。因此,我认为您需要创建某种数据结构,用于存储每个UITableViewCell中的文本,一个NSDictionary,其中NSIndexPaths作为键,文本作为值,会很好地完成这项工作。这样,您可以找到所需文本的高度,而无需引用UITableViewCell


我不知道如何避免引用单元格(原型),因为我需要获取文本容器的宽度来计算高度。 - Johan

1

虽然您可以在UITableViewCell子类的“-layoutSubviews”中真正动态地计算表视图单元格中包含的标签的高度,但我不知道有没有类似的方法来处理表视图委托的“-tableView:heightForRowAtIndexPath:”中的单元格高度。

请考虑以下内容:

- (void)layoutSubviews
{
    [super layoutSubviews];

    CGSize size = [self.textLabel.text sizeWithFont:self.textLabel.font
                                  constrainedToSize:CGSizeMake(self.textLabel.$width, CGFLOAT_MAX)
                                      lineBreakMode:self.textLabel.lineBreakMode];
    self.textLabel.$height = size.height;
}

不幸的是,当调用“-tableView:heightForRowAtIndexPath:”时,这已经太早了,因为cell.textLabel.frame尚未设置为CGRectZero = {{0, 0}, {0, 0}}

据我所知,您无法使用内容视图的框架或总结各个标签的框架来完成此操作...

我能想到的唯一方法是提供一个方便类、方法、常量或其他东西,以尝试覆盖任何设备方向上的所有可能宽度,在任何设备上:

@interface UITableView (Additions)

@property (nonatomic, readonly) CGFloat padding;

@end

@implementation UITableView (Additions)

- (CGFloat)padding
{
    if (self.formStyle == PTFormViewStylePlain) {
        return 0;
    }

    if (self.$width < 20.0) {
        return self.$width - 10.0;
    }

    if (self.$width < 400.0 || [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return 10.0;
    }

    return MAX(31.0, MIN(45.0, self.$width * 0.06));
}

@end

另外请注意,最近我们也推出了新的iPhone 5,横向宽度为4英寸(568而不是480)。

整个事情相当令人不安,我知道...干杯。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接