向 UITableViewCells 添加子视图会导致滚动时出现问题

Posted

技术标签:

【中文标题】向 UITableViewCells 添加子视图会导致滚动时出现问题【英文标题】:Adding subviews to UITableViewCells causing issue while scrolling 【发布时间】:2013-03-03 16:15:06 【问题描述】:

我完全不知道如何解决这个问题。 所以我有一个 UITableView 并且在委托方法 cellForRowAtIndex: 如果单元格为 nil (表格视图的初始构建),我将向每个单元格添加几个子视图。一切正常,表格视图已构建,但是,当我在应用程序中向下滚动一点时,应用程序突然与 SIGBART 崩溃并给我错误 * 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“+[NSIndexPath setImage:]: unrecognized selector sent to class 0x3c361e68”** 这很奇怪,因为我是甚至没有在我的代码中的任何地方调用 setImage 方法。 这是委托方法的代码。

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


static NSString *CellIdentifier = @"Cell";
UIImageView* imageView;
UILabel* ttitle;
UILabel* ttitle2;
UILabel* ttitle3;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) 
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

    // Configure cell:
    // *** This section should configure the cell to a state independent of
    //  whatever row or section the cell is in, since it is only executed
    //  once when the cell is first created.


    imageView=[[UIImageView alloc]initWithFrame:CGRectMake(10.0, 11.0, 50.0, 50.0)];
    [imageView setContentMode:UIViewContentModeScaleAspectFill];
    imageView.layer.masksToBounds=YES;
    imageView.layer.cornerRadius=5.0;
    [cell.contentView addSubview:imageView];

    ttitle = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 7.0, 200, 20)] autorelease];
    ttitle.textColor= [UIColor blackColor];
    ttitle.numberOfLines=1;
    ttitle.backgroundColor=[UIColor clearColor];
    ttitle.font=[UIFont fontWithName:@"Arial Bold" size:15.0];
    [cell.contentView addSubview:ttitle];

    if (indexPath.row==0) 

        CGSize size=[[[data objectAtIndex:indexPath.row] valueForKey:@"content"] sizeWithFont:[UIFont systemFontOfSize:14.0f] constrainedToSize:CGSizeMake(265.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        ttitle2 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 27.5, 200, size.height)] autorelease];
        ttitle2.textColor= [UIColor darkGrayColor];
        ttitle2.backgroundColor=[UIColor clearColor];
        ttitle2.numberOfLines=0;
        ttitle2.textAlignment = NSTextAlignmentLeft;
        ttitle2.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle2.font=[UIFont fontWithName:@"Arial" size:14.0];
        [cell.contentView addSubview:ttitle2];

        ttitle3 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, ttitle2.frame.origin.y+ttitle2.frame.size.height-8.0, 210, 40)] autorelease];
        ttitle3.textColor= [UIColor darkGrayColor];
        ttitle3.backgroundColor=[UIColor clearColor];
        ttitle3.numberOfLines=1;
        ttitle3.textAlignment = NSTextAlignmentLeft;
        ttitle3.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle3.font=[UIFont fontWithName:@"Arial" size:11.0];
        [cell.contentView addSubview:ttitle3];


    
    else

        CGSize size=[[[data objectAtIndex:indexPath.row] valueForKey:@"content"] sizeWithFont:[UIFont systemFontOfSize:14.0f] constrainedToSize:CGSizeMake(265.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        ttitle2 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 27.0, 200, size.height)] autorelease];
        ttitle2.textColor= [UIColor darkGrayColor];
        ttitle2.backgroundColor=[UIColor clearColor];
        ttitle2.numberOfLines=0;
        ttitle2.textAlignment = NSTextAlignmentLeft;
        ttitle2.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle2.font=[UIFont fontWithName:@"Arial" size:14.0];
        [cell.contentView addSubview:ttitle2];


        ttitle3 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, ttitle2.frame.origin.y+ttitle2.frame.size.height-9.0, 210, 40)] autorelease];
        ttitle3.textColor= [UIColor darkGrayColor];
        ttitle3.backgroundColor=[UIColor clearColor];
        ttitle3.numberOfLines=1;
        ttitle3.textAlignment = NSTextAlignmentLeft;
        ttitle3.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle3.font=[UIFont fontWithName:@"Arial" size:11.0];
        [cell.contentView addSubview:ttitle3];

    



// Customize cell:
// *** This section should customize the cell depending on what row or section
//  is passed in indexPath, since this is executed every time this delegate method
//  is called.
imageView.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[[data objectAtIndex:indexPath.row] valueForKey:@"thumbnail"]]]];

[ttitle setText:[[data objectAtIndex:indexPath.row] valueForKey:@"name"]];
[ttitle2 setText:[[data objectAtIndex:indexPath.row] valueForKey:@"content"]];

NSString* first=[[[data objectAtIndex:indexPath.row] valueForKey:@"hashtag"] stringByAppendingString:@"     "];
NSString* second =[first stringByAppendingString:[[data objectAtIndex:indexPath.row] valueForKey:@"place"]];
NSString* third=[second stringByAppendingString:@"        "];
NSString* fourth=[third stringByAppendingString:@"¤ "];
NSString* conversion=[[[data objectAtIndex:indexPath.row] valueForKey:@"counter"] stringValue];
NSString* fifth=[fourth stringByAppendingString:conversion];
[ttitle3 setText:fifth];





return cell;

感谢帮助的家伙!

*更新代码

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


static NSString *CellIdentifier = @"Cell";
UIImageView* imageView;
UILabel* ttitle;
UILabel* ttitle2;
UILabel* ttitle3;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) 
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

    // Configure cell:
    // *** This section should configure the cell to a state independent of
    //  whatever row or section the cell is in, since it is only executed
    //  once when the cell is first created.


    imageView=[[UIImageView alloc]initWithFrame:CGRectMake(10.0, 11.0, 50.0, 50.0)];
    [imageView setContentMode:UIViewContentModeScaleAspectFill];
    imageView.layer.masksToBounds=YES;
    imageView.layer.cornerRadius=5.0;
    imageView.tag=1;
    [cell.contentView addSubview:imageView];

    ttitle = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 7.0, 200, 20)] autorelease];
    ttitle.textColor= [UIColor blackColor];
    ttitle.numberOfLines=1;
    ttitle.tag=69;
    ttitle.backgroundColor=[UIColor clearColor];
    ttitle.font=[UIFont fontWithName:@"Arial Bold" size:15.0];
    [cell.contentView addSubview:ttitle];

    if (indexPath.row==0) 

        CGSize size=[[[data objectAtIndex:indexPath.row] valueForKey:@"content"] sizeWithFont:[UIFont systemFontOfSize:14.0f] constrainedToSize:CGSizeMake(265.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        ttitle2 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 27.5, 200, size.height)] autorelease];
        ttitle2.textColor= [UIColor darkGrayColor];
        ttitle2.backgroundColor=[UIColor clearColor];
        ttitle2.numberOfLines=0;
        ttitle2.tag=70;
        ttitle2.textAlignment = NSTextAlignmentLeft;
        ttitle2.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle2.font=[UIFont fontWithName:@"Arial" size:14.0];
        [cell.contentView addSubview:ttitle2];

        ttitle3 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, ttitle2.frame.origin.y+ttitle2.frame.size.height-8.0, 210, 40)] autorelease];
        ttitle3.textColor= [UIColor darkGrayColor];
        ttitle3.backgroundColor=[UIColor clearColor];
        ttitle3.numberOfLines=1;
        ttitle3.tag=71;
        ttitle3.textAlignment = NSTextAlignmentLeft;
        ttitle3.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle3.font=[UIFont fontWithName:@"Arial" size:11.0];
        [cell.contentView addSubview:ttitle3];


    
    else

        CGSize size=[[[data objectAtIndex:indexPath.row] valueForKey:@"content"] sizeWithFont:[UIFont systemFontOfSize:14.0f] constrainedToSize:CGSizeMake(265.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        ttitle2 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, 27.0, 200, size.height)] autorelease];
        ttitle2.textColor= [UIColor darkGrayColor];
        ttitle2.backgroundColor=[UIColor clearColor];
        ttitle2.numberOfLines=0;
        ttitle2.tag=70;
        ttitle2.textAlignment = NSTextAlignmentLeft;
        ttitle2.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle2.font=[UIFont fontWithName:@"Arial" size:14.0];
        [cell.contentView addSubview:ttitle2];


        ttitle3 = [[[UILabel alloc] initWithFrame:CGRectMake(70.0, ttitle2.frame.origin.y+ttitle2.frame.size.height-9.0, 210, 40)] autorelease];
        ttitle3.textColor= [UIColor darkGrayColor];
        ttitle3.backgroundColor=[UIColor clearColor];
        ttitle3.numberOfLines=1;
        ttitle3.tag=71;
        ttitle3.textAlignment = NSTextAlignmentLeft;
        ttitle3.lineBreakMode=NSLineBreakByWordWrapping;
        ttitle3.font=[UIFont fontWithName:@"Arial" size:11.0];
        [cell.contentView addSubview:ttitle3];

    

    imageView.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[[data objectAtIndex:indexPath.row] valueForKey:@"thumbnail"]]]];

    [ttitle setText:[[data objectAtIndex:indexPath.row] valueForKey:@"name"]];
    [ttitle2 setText:[[data objectAtIndex:indexPath.row] valueForKey:@"content"]];

    NSString* first=[[[data objectAtIndex:indexPath.row] valueForKey:@"hashtag"] stringByAppendingString:@"     "];
    NSString* second =[first stringByAppendingString:[[data objectAtIndex:indexPath.row] valueForKey:@"place"]];
    NSString* third=[second stringByAppendingString:@"        "];
    NSString* fourth=[third stringByAppendingString:@"¤ "];
    NSString* conversion=[[[data objectAtIndex:indexPath.row] valueForKey:@"counter"] stringValue];
    NSString* fifth=[fourth stringByAppendingString:conversion];
    [ttitle3 setText:fifth];



else 
    imageView =[cell viewWithTag:1];
    ttitle=[cell viewWithTag:69];
    ttitle2=[cell viewWithTag:70];
    ttitle3=[cell viewWithTag:71];


//STUFFOUTSIDE

// Customize cell:
// *** This section should customize the cell depending on what row or section
//  is passed in indexPath, since this is executed every time this delegate method
//  is called.



return cell;

【问题讨论】:

这是一个内存管理问题。尝试使用 Zombies 仪器跑步 配置自定义单元类然后调用它们以获得一些 indexPath 值会更好吗?希望这会有所帮助! 不,实际上,这不是内存管理问题... 【参考方案1】:

问题是当重新使用单元格时,您的局部变量没有被初始化。这是 imageView 的当前流程:

UIImageView* imageView;

if (cell == nil)

    // Create imageView
    imageView=...


// If cell is being reused (ie cell is not nil) then imageView is nil at this point.
imageView.image=...

当您重用一个表格视图单元格时,tableView:dequeueReusableCellWithIdentifier: 返回一个实际的单元格而不是 nil,并跳过 imageView 的初始化。

您需要“找到”重用单元格中的 imageView 才能对其进行更改。

因此,我建议你这样修改你的逻辑:

UIImageView* imageView;

if (cell == nil)

    // Create imageView
    imageView=...

else

    imageView = // get a reference to the imageView


imageView.image=...

所以现在,当然,问题是“如何?”。

一种很常见的方法是在创建视图时设置视图的tag,以便以后轻松检索。您可以像这样使用这种技术:

// Use a unique tag number for each subview.
#define MY_IMAGEVIEW_TAG 1000

UIImageView* imageView;

if (cell == nil)

    // Create imageView
    imageView=... // Same as before
    imageView.tag = MY_IMAGEVIEW_TAG; 

else

    // This is a cell that is being re-used and was previously created.
    // Retrieve a reference to the existing image view that is already in the cell
    imageView = [cell viewWithTag:MY_IMAGEVIEW_TAG];


// Now imageView is "safe" to use whether it is a new cell or one that is reused!
imageView.image=...

注意:如果您经常这样做,创建一个具有每个子视图属性的 UITableViewCell 子类将使标签和 viewWithTag 的使用变得不必要,并且使您的代码更易于阅读。

【讨论】:

好吧好吧,我知道你在做什么,它运作良好。唯一的事情是,使用这种方法,数据有时会重新排列。我的意思是 tableView 将出于某种原因重绘单元格(我不明白,因为我虽然使用这种类型的流程的全部目的是避免这样的冲突)。有什么想法吗? 我在这里看不到任何会改变您的数据源的东西。在别处寻找为什么会发生这种情况! :-) 好吧,我不认为数据源在改变,tableView只是在不同位置重绘单元格。 哦,正如@F***Kreiser 指出的那样,以imageView.image=[UIImage imageWithData:[NSData... 开头的代码应该位于方法的底部,其中局部变量已设置为新视图或从现有单元格中检索.您仅在创建它时设置它(因此它正在“重用”创建它的 indexPath 中的值。) 您还应该在方法底部更改ttitle2ttitle3 的大小,因为它会根据行和数据本身而变化。【参考方案2】:

@Inafziger 已经发布了这个问题的正确答案,我只想更详细地解释一下为什么你会看到这个“奇怪”的崩溃。 不过,我不建议过度使用标签。创建UITableViewCell 的子类可能是一个更好的主意。

您没有初始化您的 imageViewttitle 变量:

UIImageView *imageView; // imageView can point to anything now!
UILabel* ttitle;

通常,当您声明局部变量以避免此类悬空指针时,您会将它们初始化为 nil0(任何有意义的)。

因为您正在重用您的表格视图单元格 cell 并不总是 nil 并且您的 if 子句不会被执行:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)  // Won't be executed if the cell could be dequeued!
    ...
    imageView = ...;

因此,如果cell 可以出队,那么您的imageViewttitle 变量在您使用它们时仍然没有被分配给任何东西!

然后您将设置视图的属性:

imageView.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[[data objectAtIndex:indexPath.row] valueForKey:@"thumbnail"]]]];
[ttitle setText:[[data objectAtIndex:indexPath.row] valueForKey:@"name"]];
[ttitle2 setText:[[data objectAtIndex:indexPath.row] valueForKey:@"content"]]

imageView.image = ...; 与调用[imageView setImage:...]; 相同。你可以在这里阅读更多信息:http://www.cocoacast.com/cocoacast/?q=node/103

这时候所有的东西都在一起了:imageView 没有初始化,你正在调用-setImage:。轰隆隆,崩溃!

在您的情况下,imageView 指向 NSIndexPath 类。不过,这可以是任何东西。 因此,您实际上是在 NSIndexPath 类上调用 -setImage:(相当于:+[NSIndexPath setImage:])并且应用程序崩溃并显示 +[NSIndexPath setImage:]: unrecognized selector sent to class 0x3c361e68 错误消息。

【讨论】:

谢谢你的解释!但有一件事我不明白。当我们向 UITableViewCells 添加子视图时,我们希望避免一遍又一遍地这样做,这样它就不会在每次重用单元格时重绘这些对象。如果是这样,为什么这种方法不能正常工作?我实现了标签来创建对子视图的引用,但是 tableView 会从顶部的底部随机重绘单元格,反之亦然... 不要移动if (cell == nil) 子句中的自定义代码。将其保留在原处,仅添加 else imageView = [cell viewWithTag:1]; ... 对,我没有。 (在上面发布了更新的代码)。我只是不太明白为什么 tableView 会在这一点上做这样的事情。前任。取出最后一个单元格并在顶部重新绘制它。

以上是关于向 UITableViewCells 添加子视图会导致滚动时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

无法滚动包含 UITextFields 的 UITableViewCells

iPhone:向 Cell ContentView 添加子视图会在滚动时覆盖单元格文本

使用 Masonry 向视图的子视图添加自动布局约束

将子视图添加到 UITableViewCell contentView 的正确方法

UITableViewCells 周围的黑角

试图向视图添加子视图,但子视图不显示