我在这里有一个简单的示例项目:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
在这个项目中,UITableView有一个自定义的UITableViewCell。每个单元格中都有3个包含标签的UIView。
目标是在敲击时扩展单元格,然后在再次敲击时将其折叠。单元格本身必须更改其高度以显示子视图。插入或删除行是不可接受的。
演示项目几乎按预期工作。事实上,在iOS 4.3中,它的工作非常完美。然而,在iOS 5下,当行崩溃时,先前的单元格神奇地消失。
要重新创建问题,请在具有iOS 5的模拟器或设备中运行项目,然后点击第一个单元格以展开它。然后再次点击该单元格以折叠它。最后,直接点击它下面的单元格。前一个消失了。
继续点击该部分中的每个单元格将导致所有单元格消失,直至缺少整个部分。
我也尝试过使用reloadData而不是当前的设置,但这会破坏动画并且感觉有点像黑客。 reloadRowsAtIndexPaths应该可以工作,但问题是为什么不呢?
查看下面发生的事情的图片:
表格出现:
细胞扩张:
细胞坍塌:
细胞消失(当点击下面的细胞时):
不断重复,直到整个部分消失:
修改 覆盖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方法。
答案 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