向 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 中的值。)
您还应该在方法底部更改ttitle2
和ttitle3
的大小,因为它会根据行和数据本身而变化。【参考方案2】:
@Inafziger 已经发布了这个问题的正确答案,我只想更详细地解释一下为什么你会看到这个“奇怪”的崩溃。
不过,我不建议过度使用标签。创建UITableViewCell
的子类可能是一个更好的主意。
您没有初始化您的 imageView
和 ttitle
变量:
UIImageView *imageView; // imageView can point to anything now!
UILabel* ttitle;
通常,当您声明局部变量以避免此类悬空指针时,您会将它们初始化为 nil
或 0
(任何有意义的)。
因为您正在重用您的表格视图单元格 cell
并不总是 nil
并且您的 if 子句不会被执行:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) // Won't be executed if the cell could be dequeued!
...
imageView = ...;
因此,如果cell
可以出队,那么您的imageView
和ttitle
变量在您使用它们时仍然没有被分配给任何东西!
然后您将设置视图的属性:
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 添加子视图会在滚动时覆盖单元格文本