【问题标题】:How to set the table view cell accessory view to retain a previously initialized UIImageView?如何设置表格视图单元格附件视图以保留先前初始化的 UIImageView?
【发布时间】:2011-03-12 13:34:10
【问题描述】:

假设我的视图控制器中有一个属性,定义如下:

@property (nonatomic, retain) UIImageView *checkmarkOffAccessoryView;

我在实现中@synthesize这个,release它在-dealloc中并在-viewDidLoad中初始化它如下:

self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"checkmarkOff.png"]] autorelease];

到目前为止一切顺利。

当我在我的表格视图委托中使用它作为多个单元格的辅助视图时,会发生两件事:

  1. 只有一个单元格的附件视图显示图像
  2. 应用程序 UI 冻结。

应用程序没有崩溃,据我所知,用户界面只是变得无响应。这在模拟器和设备上都有。

这是我如何在单元格中使用初始化属性:

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

    // initialize or dequeue cell...

    if (condition)
        cell.accessoryView = self.checkmarkOffAccessoryView;
    else
        cell.accessoryView = nil;
}

使用上述代码,只有一个单元格显示附件视图并且 UI 冻结。

如果我直接在委托方法中初始化 UIImageView 实例,我会得到所有满足条件的单元格显示附件视图,并且我不会遇到 UI 冻结:

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

    // initialize or dequeue cell...

    if (condition)
        cell.accessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"checkmarkOff.png"]] autorelease];
    else
        cell.accessoryView = nil;
}

我的目标是初始化尽可能少的对象并重用一个UIImageView。我很好奇为什么第一段代码有问题,我能做些什么来解决这个问题。

似乎单元格的accessoryView 属性应该只是增加self.checkmarkOffAccessoryViewretain 计数,但似乎我缺少一些细节。

我忽略了什么?谢谢你的建议。

编辑

我认为:

self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"checkmarkOff.png"]] autorelease];

等同于:

UIImageView *uncheckedView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"checkmarkOff.png"]];
self.checkmarkOffAccessoryView = uncheckedView;
[uncheckedView release];

无论哪种方式,我都会遇到相同的冻结症状。

【问题讨论】:

  • 如果您不使用点符号来访问该属性会发生什么?即 cell.accessoryView = checkmarkOffAccessoryView_;
  • Alex:我不确定我的回复有什么不足,这触发了您的赏金,但您可能想评论并澄清为什么答案不足,以便回复者可以说“哦,好吧”或调整他们的答案以更好地适应您的问题。

标签: iphone uitableview uiimageview


【解决方案1】:

您不能多次添加同一个视图。 UI 处理程序将变得疯狂。为了确保这一点,我尝试按照您上面所说的进行操作,但遇到了同样的问题。 UI 冻结,图像仅出现在其中一个单元格中。

您可以做的最好的事情是将您的图像存储为分配的 UIImage,并拥有一个帮助函数,该函数为每个单元格返回一个新的 UIImageView。

使用您当前的方法(没有存储的 UIImage)您可能会这样做:

-(UIImageView *) makeCheckmarkOffAccessoryView
{
    return [[[UIImageView alloc] initWithImage:
        [UIImage imageNamed:@"checkmarkOff.png"]] autorelease];
}

然后做

cell.accessoryView = [self makeCheckmarkOffAccessoryView];

您可能知道,另一方面,UIImages 可以使用任意次数。一个 UIImageView 不会占用太多空间,所以你可以轻松拥有一堆而不用担心。

要扩展一个地方的交易,假设您同时将一个 UIView 添加到两个地方。

[ob removeFromSuperview] 会为这个对象做什么?它会从两个地方删除视图吗?仅从其中之一?请求 [ob superview] 时会返回哪个值?显然,UI 不是用来处理您所要求的。

【讨论】:

    【解决方案2】:

    尝试在初始化程序中不使用自动释放。我怀疑你过度释放了。

    顺便说一句,您的控制台在冻结时可能会显示 BAD_ACCESS 错误。如果你打开 NSZombieEnabled,我猜你会看到它正在调用一个已释放的 UIImage。

    【讨论】:

    • 他正在调用 initWithImage,如果他不通过自动释放(或显式)释放它,他将泄漏对象......(除非我错过了什么)。
    • 没有抛出异常。我很确定我没有过度释放。
    • 我这么说是因为您将它分配给使用retain setter 语义合成的属性,然后您在-dealloc 时间手动释放它。在我看来,自动释放是不必要的,在最坏的情况下,它会导致偶尔访问空指针。如果我是你,只是为了好玩,我会关闭自动释放,看看它是否能解决问题。可能不会,但这是我首先要攻击它的地方。
    • 请查看编辑,我在其中显示了遵循相同内存管理规则的替代实例化。
    【解决方案3】:

    也许这会有所帮助

    - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    {
        static NSString *CellIdentifier = @"ShoppingListCell";
    
        HSShoppingListCell *cell = (HSShoppingListCell *)[aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            [[NSBundle mainBundle] loadNibNamed:@"ShoppingListCell" 
                                                                                owner:self 
                                                                            options:nil];
            cell = shoppingListCell;
        }
    
        ShoppingListItem *theItem = nil;
        theItem = [self.fetchedResultsController objectAtIndexPath:indexPath];
    
        UIImage *selected         = [UIImage imageNamed:@"listBullet_checked.png"];
        UIImage *notSelected    = [UIImage imageNamed:@"listBullet.png"];
    
        cell.imageView.image = ([theItem.checkedOff boolValue] ? selected : notSelected); 
    
        cell.shoppingListLabel.text = theItem.productName;
        [cell.shoppingListLabel setFont:[UIFont fontWithName:@"Marker Felt" size:26.0]];
        return cell;
    }
    
    - (void)toggleCellImage:(NSIndexPath *)indexPath
    {
        ShoppingListItem *item  = [self.fetchedResultsController objectAtIndexPath:indexPath];
    
        item.checkedOff = ([item.checkedOff boolValue] ? [NSNumber numberWithBool:NO] : [NSNumber numberWithBool:YES]);
    
        [HSCoreDataUtilities saveContext:item.managedObjectContext];
        [self.tableView reloadData];
    }
    
    #pragma mark -
    #pragma mark Table view delegate
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
    {
        [self toggleCellImage:indexPath];
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    

    【讨论】:

      【解决方案4】:

      将您的案例简化为基本要素(我本来建议在 UIImageView... 周围放置两个“瘦”UIView 对象),我发现这很可能是不可能的。

      在 IB 中创建 2 个空的 UIView 对象,将它们连接到 bareView1bareView2。那么

      UIImageView *imageView = [[UIImageView alloc]
                            initWithImage:[UIImage imageNamed:@"test.png"]];
      [bareView1 addSubview:imageView]; // it shows either here ...
      [bareView2 addSubview:imageView]; // ... or here
      

      您永远不能像这样在屏幕上多次看到图像。根据经验,我认为UIView继承的第一个对象可以多次使用,即UIImage。正如 Kalle 所说,UIView 在视图层次结构中只能有一个父级。

      推迟第二个addSubview 只会使UIImageViewbareView1 跳转到bareView2

      冻结的发生可能是因为事件处理混淆了:附件可以是交互式的,如果它们是同一个对象,你怎么知道哪个被点击了?所以代码假定对象是唯一的,而你设法违反了这个假设。

      【讨论】: