使用reloadRowsAtIndexPaths重新加载时,为什么我的表视图单元格会消失?

时间:2012-03-10 19:29:34

标签: iphone objective-c ios uitableview

我在这里有一个简单的示例项目:

http://dl.dropbox.com/u/7834263/ExpandingCells.zip

在这个项目中,UITableView有一个自定义的UITableViewCell。每个单元格中都有3个包含标签的UIView。

目标是在敲击时扩展单元格,然后在再次敲击时将其折叠。单元格本身必须更改其高度以显示子视图。插入或删除行是不可接受的。

演示项目几乎按预期工作。事实上,在iOS 4.3中,它的工作非常完美。然而,在iOS 5下,当行崩溃时,先前的单元格神奇地消失。

要重新创建问题,请在具有iOS 5的模拟器或设备中运行项目,然后点击第一个单元格以展开它。然后再次点击该单元格以折叠它。最后,直接点击它下面的单元格。前一个消失了。

继续点击该部分中的每个单元格将导致所有单元格消失,直至缺少整个部分。

我也尝试过使用reloadData而不是当前的设置,但这会破坏动画并且感觉有点像黑客。 reloadRowsAtIndexPaths应该可以工作,但问题是为什么不呢?

查看下面发生的事情的图片:

表格出现:

table appears

细胞扩张:

table expands

细胞坍塌:

table collapses

细胞消失(当点击下面的细胞时):

cell disappears

不断重复,直到整个部分消失:

section disappears

修改 覆盖alpha是一个黑客,但有效。这是另一个“黑客”,它也修复了它,但它为什么要修复它?

JVViewController.m第125行:

if( previousIndexPath_ != nil )
{
    if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;

    JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];

    BOOL expanded = [previousCell expanded];
    if( expanded )
    {
        [previousCell setExpanded:NO];
        [indicesToReload addObject:[previousIndexPath_ copy]];
    }
    else if( currentCellSameAsPreviousCell )
    {
        [previousCell setExpanded:YES];
        [indicesToReload addObject:[previousIndexPath_ copy]];
    }

    //[indicesToReload addObject:[previousIndexPath_ copy]];
}

编辑2:

对演示项目进行了一些小改动,值得检查并查看JVViewController didSelectRowAtIndexPath方法。

12 个答案:

答案 0 :(得分:21)

您的问题出在setExpanded:在JVCell.m中,您正在直接编辑该方法中目标单元格的框架。

- (void)setExpanded:(BOOL)expanded
{
    expanded_ = expanded;

    CGFloat newHeight = heightCollapsed_;
    if( expanded_ ) newHeight = heightExpanded_;

    CGRect frame = self.frame;
    frame.size.height = newHeight;
    self.frame = frame;
}

将其更新为:

- (void)setExpanded:(BOOL)expanded
{
    expanded_ = expanded;
}

然后在JVViewController.m的第163行删除对-reloadRowsAtIndexPaths:withRowAnimation:的调用,它将按预期动画。

-reloadRowsAtIndexPaths:withRowAnimation:期望为提供的indexPaths返回不同的单元格。由于您只调整尺寸-beginUpdates& -endUpdates足以再次布置表格视图单元格。

答案 1 :(得分:9)

可能是我错过了一个观点,但为什么不使用:

UITableViewRowAnimationNone

我的意思是代替:

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];

使用

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];

答案 2 :(得分:8)

为tableView调用高度变化设置动画。

[tableView beginUpdates];
[tableView endUpdates];

不要调用reloadRowsAtIndexPaths:

请参阅Can you animate a height change on a UITableViewCell when selected?

答案 3 :(得分:5)

淡出的单元格是前一个不改变大小的单元格。正如reloadRowsAtIndexPaths:withRowAnimation:所述的文档:

  

该表为新单元格设置动画,因为它为旧行设置了动画。

不透明度设置为1,然后立即设置为0,因此淡出。

如果前一个和新一个单元格都改变了大小,那么它按预期工作。这是因为开始/结束更新会注意到高度的变化,并在覆盖reloadRowsAtIndexPaths:withRowAnimation:个的单元格上创建新的动画。

您的问题是由于在用于加载新单元格时滥用reloadRowsAtIndexPaths:withRowAnimation:来调整单元格的大小。

但你根本不需要reloadRowsAtIndexPaths:withRowAnimation:。只需更改单元格的展开状态并执行开始/结束更新。这将为你处理所有动画。

作为旁注,我发现蓝色选择有点烦人,在JVCell中将selectedBackgroundView设置为与backgroundView相同的图像(或创建具有所选单元格的正确外观的新图像)。


编辑:

previousIndexPath_添加到indicesToReload的语句移动到if语句(第132行),这样只有在前一个单元格被展开并且需要调整大小时才会添加它。

if( expanded ) {
    [previousCell setExpanded:NO];
    [indicesToReload addObject:[previousIndexPath_ copy]];
}

这将删除先前折叠的单元格将消失的情况。

另一种选择是在当前单元格折叠时将previousIndexPath_设置为nil,并仅在单元格展开时设置它。

这仍然像一个黑客。同时执行reloadRows和开始/结束更新会导致tableView重新加载所有内容两次,但似乎都需要正确设置动画。我想如果表格不是太大,这不会是性能问题。

答案 4 :(得分:2)

简短实用的答案:将UITableViewRowAnimationAutomatic更改为UITableViewRowAnimationTop可以解决问题。没有更多的行消失! (在iOS 5.1上测试)

另一个简短实用的答案,因为UITableViewRowAnimationTop据说导致了它自己的问题:创建一个新的单元格视图而不是修改现有的框架。在真实的应用程序中,单元格视图中显示的数据应该在应用程序的模型部分中,因此如果设计得当,创建另一个仅以不同方式显示相同数据的单元格视图应该不成问题。 (在我们的例子中框架)。

关于动画重新加载同一个单元格的更多想法:

在某些情况下,

UITableViewRowAnimationAutomatic似乎已解决UITableViewRowAnimationFade,即当您看到细胞逐渐消失并消失时。新单元应该淡入,而旧单元淡出。但是旧电池和新电池是同一个 - 所以,这甚至可以工作吗?在核心动画级别,是否可以淡出视图并同时淡入它?听起来很可疑。结果就是你只看到了淡出。这可能被认为是一个Apple错误,因为预期的行为可能是如果相同的视图已经改变,alpha属性将不会被动画化(因为它不能同时为0和1设置动画),但是而只是框架,颜色等将动画。

请注意,问题只出现在动画的显示中 - 如果您向外滚动并向后滚动,一切都会正确显示。

在iOS 4.3中,Automatic模式可能已被解析为Fade之外的其他内容,这就是为什么事情在那里工作(就像你写的那样) - 我没有深入研究。

我不知道为什么iOS会选择Fade模式。但的情况之一就是当你的代码重新加载一个先前被攻击的单元格时,它被折叠,并且与当前的单击单元格不同。请注意,以前点击的单元格总是重新加载,代码中的这一行始终称为:

[indicesToReload addObject:[previousIndexPath_ copy]];

这解释了您所描述的神奇消失细胞情景。

顺便说一句,beginUpdates / endUpdates看起来像是对我的黑客攻击。这对调用只是包含动画,除了你已经要求重新加载的行之外,你没有添加任何动画。在这种情况下它所做的一切都是神奇地导致Automatic模式在某些情况下不选择Fade - 但这只是模糊了问题。

最后一点:我玩Top模式并发现它也会导致问题。例如,插入以下代码会使细胞消失:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}

不确定这里是否存在真正的问题(类似于同时淡入和淡出视图的问题),或者可能是Apple漏洞。

答案 5 :(得分:1)

我刚下载了你的项目&在didSelectRowAtIndexPath委托中找到了此部分代码,其中使用了reloadRowsAtIndexPaths

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];

而不是以上为什么不试试这个?

[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];

我建议这样做的原因是我认为reloadRowsAtIndexPaths:...仅在调用以下内容时有效:

- (void)beginUpdates;
- (void)endUpdates;

除此之外,行为未定义,正如您所发现的那样,相当不可靠。引用“Table View Programming Guide for iPhone OS”的相关部分:

  

要为行和节的批量插入和删除设置动画,请在连续调用beginUpdates和endUpdates定义的动画块中调用插入和删除方法。如果不在此块中调用插入和删除方法,则行和节索引可能无效。 beginUpdates ... endUpdates块不可嵌套。

     

在一个块结束时 - 也就是说,在endUpdates返回之后 - 表视图查询其数据源并像往常一样委托行和段数据。因此,应更新支持表视图的集合对象以反映新的或删除的行或节。

     

reloadSections:withRowAnimation:和reloadRowsAtIndexPaths:withRowAnimation:方法,在iPhone OS 3.0中引入,与上面讨论的方法有关。它们允许您请求表视图重新加载特定部分和行的数据,而不是通过调用reloadData来加载整个可见表视图。

可能有其他正当理由,但让我稍微考虑一下,因为我也有你的代码,我可以用它来解决。希望我们应该弄明白......

答案 6 :(得分:1)

使用此方法时,您必须确保自己在主线程上。 刷新UITableViewCell如下所示应该可以做到这一点:

- (void) refreshTableViewCell:(NSNumber *)row
{
    if (![[NSThread currentThread] isMainThread])
    {
        [self performSelector:_cmd onThread:[NSThread mainThread]  withObject:row waitUntilDone:NO];
        return;
    }

    /*Refresh your cell here
     ...
     */

}

答案 7 :(得分:0)

@Javy,我在测试你的应用时发现了一些奇怪的行为。

iPhone 5.0模拟器上运行时,previousIndexpth_变量为

class NSArray(看起来像。)这里是调试器输出

 (lldb) po previousIndexPath_
    (NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(

    <JVSectionData: 0x6a61230>,

    <JVSectionData: 0x6a64920>,

    <JVSectionData: 0x6a66260>
    )

    (lldb) po [previousIndexPath_ class]

    (id) $7 = 0x0145cb64 __NSArrayI

iPhone 4.3模拟器中,它的类型为NSIndexPath

lldb) po [previousIndexPath_ class]

(id) $5 = 0x009605c8 NSIndexPath

(lldb) po previousIndexPath_

(NSIndexPath *) $6 = 0x04b5a570 <NSIndexPath 0x4b5a570> 2 indexes [0, 0]

你知道这个问题吗?不确定这是否会有所帮助,但想到让你知道。

答案 8 :(得分:0)

我认为当单元格未展开时,您不应保留以前的indexPath, 尝试修改你做了如下所示的选择方法,它的工作正常..

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL currentCellSameAsPreviousCell = NO;
NSMutableArray *indicesToReload = [NSMutableArray array];

if(previousIndexPath_ != nil)
{
    if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;

    JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];

    BOOL expanded = [previousCell expanded];
    if(expanded) 
    {
     [previousCell setExpanded:NO];
    }
    else if  (currentCellSameAsPreviousCell)
    {
        [previousCell setExpanded:YES];
    }
    [indicesToReload addObject:[previousIndexPath_ copy]];

    if (expanded)
        previousIndexPath_ = nil;
    else
        previousIndexPath_ = [indexPath copy];         
}

if(currentCellSameAsPreviousCell == NO)
{
    JVCell *currentCell = (JVCell*)[self cellForIndexPath:indexPath];

    BOOL expanded = [currentCell expanded];
    if(expanded) 
    {
        [currentCell setExpanded:NO];
        previousIndexPath_ = nil;
    }

    else
    {
        [currentCell setExpanded:YES];
        previousIndexPath_ = [indexPath copy];
    }

    // moving this line to inside the if statement blocks above instead of outside the loop works, but why?
    [indicesToReload addObject:[indexPath copy]];


}

// commenting out this line makes the animations work, but the table view background is visible between the cells

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];

// using reloadData completely ruins the animations
[tableView beginUpdates];

 [tableView endUpdates];
}

答案 9 :(得分:0)

此问题是由在cellForRowAtIndexPath中返回缓存的单元格引起的。 reloadRowsAtIndexPaths期望从cellForRowAtIndexPath获取新的新单元格。 如果你这样做,你会没事的......不需要解决方法。

来自Apple doc: “重新加载行会导致表视图向其数据源询问该行的新单元格。”

答案 10 :(得分:0)

我有一个类似的问题,我希望在激活开关以显示单元格时扩展单元格,并且单元格中的额外标签和按钮通常在单元格处于默认高度时隐藏(44)。我尝试了各种版本的reloadRowsAtPath无济于事。最后,我决定通过在heightForRowAtIndexPath中添加条件来保持简单,如下所示:

    override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if ( indexPath.row == 2){
        resetIndexPath.append(indexPath)
        if resetPassword.on {
            // whatever height you want to set the row to
            return 125
        }
    }
    return 44
}

无论你想要触发扩展单元格的代码,只需插入tableview.reloadData()即可。在我的情况下,当打开开关以指示重置密码的愿望时。

        @IBAction func resetPasswordSwitch(sender: AnyObject) {

        tableView.reloadData()
        }

这种方法没有任何滞后,没有明显的方法可以看到表重新加载,并且销售的扩展是按照您的预期逐步完成的。希望这有助于某人。

答案 11 :(得分:-1)

试试这个希望它会对你有所帮助Cell Expansion