【问题标题】:Why do my table view cells disappear when reloaded using reloadRowsAtIndexPaths?为什么使用 ReloadRows At IndexPath 重新加载时我的表格视图单元格会消失?
【发布时间】:2012-03-27 19:33:32
【问题描述】:

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

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

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

目标是在点击时展开单元格,然后在再次点击时将其折叠。单元格本身必须改变它的高度以暴露子视图。插入或删除行是不可接受的。

演示项目几乎按预期工作。事实上,在 iOS 4.3 中它可以完美运行。然而,在 iOS 5 下,当行折叠时,之前的单元格会神奇地消失。

要重现问题,请在模拟器或装有 iOS 5 的设备中运行项目,然后点击第一个单元格以展开它。然后再次点击单元格以折叠它。最后,点击其正下方的单元格。前一个消失了。

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

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

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

表格出现:

单元格展开:

单元格折叠:

单元格消失(点击下方的单元格时):

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

编辑: 覆盖 alpha 是一种 hack,但有效。这是另一个修复它的“hack”,但为什么它会修复它?

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 方法。

【问题讨论】:

  • 在 JVCell 中覆盖 setAlpha: 以阻止 setAlpha:0 似乎是一种解决方法,我没有看到任何负面影响,但它很难看。我会继续戳它,看看我能不能弄清楚它为什么要隐藏单元格。
  • 我有一个类似的例子你想要吗?
  • 如果它做同样的事情,当然。它需要有背景图像,并在扩展时更改单元格高度。它不能添加或删除单元格。它也必须是动画的。您可以将其发布在答案中并添加源代码或链接。您的代码有何不同之处?

标签: iphone objective-c ios uitableview


【解决方案1】:

您的问题出在 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 足以再次布局表格视图单元格。

【讨论】:

  • 作为旁注,alpha 设置为零的原因是因为该表将一个单元格同时视为旧单元格和新单元格。表格视图的行为是将旧单元格淡出,新单元格在其后面。因此,它的 alpha 被设置为 1 作为“新”单元格,但随后从 1 变为零作为“旧”单元格。
  • +1 - 验证这适用于 4.3 和 5.1 的模拟器。
  • 这是所有答案中最有意义的。如果它可以简单地自动改变它的高度,我宁愿不强制重新加载单元格。动画也很流畅。很不错!但是使用 beginUpdates 和 endUpdates 是不是“黑客”?我的代码是否正确使用了这些方法?
  • 这不是 hack,而是执行此操作的适当方法。该引用没有明确说明它,但是 begin/endUpdates 的功能之一是它将动画到一个新的布局。 beginUpdates/endUpdates 是唯一会从委托(返回您的 rowHeight 的地方)调用信息并根据更改进行动画处理的方法,而无需重新加载单元格。唯一可行的替代方法是调用 reloadData,然后自己完成从一个状态到下一个状态的所有动画,我个人会避免这样做。
  • 看起来你的答案是最合适的,尽管@Deniz 也提供了一种让它工作的方法。感谢您在这方面的帮助,我几乎认为它不会得到解决。现在我的表格视图看起来非常漂亮。再次感谢!
【解决方案2】:

可能是我遗漏了一点,但你为什么不直接使用:

UITableViewRowAnimationNone

我的意思是代替:

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];

使用

[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];

【讨论】:

  • 把所有这些麻烦都放在那个上面。这是我对这个问题的第二次赏金,我开始认为它不会得到解决。剩下的问题是为什么这样做,我应该使用开始/结束更新方法吗?谢谢丹尼兹
  • 这个解决了我的问题。
【解决方案3】:

只需调用一个tableView的高度变化动画。

[tableView beginUpdates];
[tableView endUpdates];

不要调用 reloadRowsAtIndexPaths:

Can you animate a height change on a UITableViewCell when selected?

【讨论】:

  • 我很高兴我滚动到页面底部看到答案:)
  • 你能解释一下为什么 beginupdates 应该是这种情况吗?此外,这并不能解释为什么 reloadRow 会使单元格消失@.@
  • 感谢这对我有用,应该是公认的答案!
【解决方案4】:

淡出的单元格是前一个没有改变大小的单元格。正如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,并且仅在单元格展开时设置它。

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

【讨论】:

  • 我尝试在 JVViewController (reloadRowsForIndexPaths) 中注释掉第 149 行,但高度变化仍然不能平滑地动画。请注意,当单击第二个单元格时,前一种“卡扣”是如何回到原位的?您可以在单元格之间看到表格视图背景片刻。如何避免这种情况?关于蓝色,我同意并在 JVCell setupImageView 方法中添加了几行代码,以将选定的背景图像也设置为相同的图像。这只是一个演示项目,完整的项目将使用不同的图形。下载更新后的项目。
  • 我明白你对中心动画的看法。非常奇怪的是,动画时间根据表格中的位置而有所不同。我在每个部分中创建了更多单元格,所有中心单元格的行为都相同。只有顶部和底部单元格的动画正确。
【解决方案5】:

简短而务实的回答:将UITableViewRowAnimationAutomatic 更改为UITableViewRowAnimationTop 解决了这个问题。不再有消失的行! (在 iOS 5.1 上测试)

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

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

UITableViewRowAnimationAutomatic 在某些情况下似乎会解析为UITableViewRowAnimationFade,即当您看到细胞逐渐消失和消失时。新单元格应该淡入,而旧单元格应该淡出。但是这里的旧牢房和新牢房是一回事——那么,这能行吗?在核心动画级别中,是否可以同时淡出视图并淡入?听起来很可疑。所以结果是你只看到淡出。这可能被认为是一个 Apple 错误,因为预期的行为可能是,如果相同的视图发生了变化,alpha 属性将不会被动画化(因为它不能同时动画到 0 和 1),但是而只是框架、颜色等将被动画化。

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

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

我不知道为什么 iOS 会选择 Fade 模式。但它确实的一种情况是,当您的代码要求重新加载以前点击过的单元格时,该单元格已折叠,并且与当前点击过的单元格不同。请注意,之前点击的单元格总是会重新加载,代码中的这一行是 always 调用的:

[indicesToReload addObject:[previousIndexPath_ copy]];

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

顺便说一句,beginUpdates/endUpdates 对我来说似乎是一个 hack。这对调用应该只包含动画,除了您已经要求重新加载的行之外,您没有添加任何动画。在这种情况下,它所做的只是神奇地导致 Automatic 模式在某些情况下不选择 Fade - 但这只是掩盖了问题。

最后一点:我玩过Top 模式,发现它也会导致问题。例如插入以下代码会使单元格有趣地消失:

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

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

【讨论】:

  • 感谢您的尝试,但我之前也尝试过“Top”,结果相同。起初它有效,但后来我发现它有它自己的问题。我也同意开始/结束更新是一个 hack,但我能够让它工作。我只是想知道它为什么不起作用或者它是否确实是一个错误。我还向 Apple 提交了错误报告,但我不知道它是否确实是错误。仍在等待他们的回音。
  • @Javy 使用另一个解决方案选项更新了答案 - 更改框架时使用不同的单元格。顺便说一句-您写道,您对调用-reloadData不感兴趣,但是每次为所有单元格重新显示tableView时都会调用您的-heightForRowAtIndexPath:方法,并且在您的实现中调用-cellForRowAtIndexPath:。因此,所有单元格都被重新检索,我认为这是 reloadData 所做的大部分工作。
  • 如果你尝试 reloadData 它会杀死动画。试一试。细胞会“卡入”到位而不是动画。 “更改框架时使用不同的单元格。”你能澄清更多吗?我没有看到您的回答文字有任何变化。
  • "另一个简短而实用的答案,因为据说 UITableViewRowAnimationTop 会导致它自己的问题:创建一个新的单元格视图而不是修改现有的框架。在一个真实的应用程序中,单元格视图中显示的数据是无论如何都应该在应用程序的模型部分,所以如果设计得当,创建另一个单元格视图应该不成问题,它只以不同的方式显示相同的数据(在我们的例子中是框架)。"
  • 嗯,这可以工作,但应用程序不应该这样做。它仍然是一种 hack,例如设置 alpha 或使用正在/结束更新。如果这个问题实际上是 SDK 中的错误,则可能无法解决此问题。
【解决方案6】:

我刚刚下载了您的项目并在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 返回之后——表视图查询它的数据源并像往常一样为行和节数据进行委托。因此,支持 table view 的集合对象应该更新以反映新的或删除的行或节。

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

可能还有其他正当理由,但让我稍微考虑一下,因为我也有你的代码,所以我可以乱用它。希望我们能弄清楚...

【讨论】:

  • 如果将 beginUpdates 放在 reloadRowsAtIndexPaths 之前,点击后单元格会立即消失。在 reloadRowsAtIndexPaths 工作后放置它。尝试测试项目,看看会发生什么。关于 Apple 文档,“为批量插入和删除行和部分设置动画......”我没有插入或删除行或部分,我只是更改单元格的高度,仅此而已。我正在使用重新加载,所以它知道检查他们的高度,因为它改变了。 ;)
  • 酷,让我保持这个答案不变。将尝试一些东西并就此回复您...
【解决方案7】:

当你使用这个方法时,你必须确保你在主线程上。 如下刷新 UITableViewCell 应该可以解决问题:

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

    /*Refresh your cell here
     ...
     */

}

【讨论】:

    【解决方案8】:

    我认为你不应该在单元格未展开时保留之前的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];
    }
    

    【讨论】:

    • 如果您在更改代码的情况下运行此演示,动画会捕捉而不是平稳运行,并且单元格之间的部分背景是可见的。
    • 是的,我刚刚在发布最后一条评论之前尝试了您的代码更改。请注意,在模拟器中,某些单元格之间的空间不会同时设置动画,您可以看到它们之间的背景。它看起来很糟糕。 @Saltymule 上面有一个很好的答案,它只调整单元格的大小而不是重新加载它们,效果很好。
    【解决方案9】:

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

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

    【讨论】:

      【解决方案10】:

      我有一个类似的问题,我想在激活开关以显示时扩展一个单元格,并在单元格处于默认高度 (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】:

        @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]
        

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

        【讨论】:

          【解决方案12】:

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

          【讨论】:

          • 这不适用于问题。他正在插入和删除行。要动态更改其高度的行。
          • @Javy 他并没有删除它只是检查行是否打开,如果打开,那么它将返回行数> 0,否则不会。好的np。
          • 为了公平起见,我再次查看了上面的链接,但他没有显示动画图像,这是这个问题的重要组成部分。我非常感谢您的帮助,所以请不要采取错误的方式。谢谢达拉。
          猜你喜欢
          • 1970-01-01
          • 2016-11-07
          • 1970-01-01
          • 1970-01-01
          • 2015-06-26
          • 2019-03-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多