I have a bare-bones sample project here:
我这里有一个简单的样本项目:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
In this project, a UITableView has a custom UITableViewCell. In each cell are 3 UIViews containing a label.
在这个项目中,UITableView有一个自定义的UITableViewCell。在每个单元格中有3个包含标签的uiview。
The goal is to expand the cell when tapped, then collapse it when tapped again. The cell itself must change it’s height to expose the subviews. Inserting or removing rows is unacceptable.
目标是在被选中时展开单元格,然后在再次被选中时折叠它。单元格本身必须改变它的高度来显示子视图。插入或删除行是不可接受的。
The demo project works almost as expected. In fact, in iOS 4.3 it works perfect. Under iOS 5, however, when the rows collapse, the previous cells magically disappear.
演示项目几乎像预期的那样工作。实际上,在iOS 4.3中,它运行得非常完美。然而,在ios5中,当行崩溃时,前面的单元会神奇地消失。
To re-create the problem, run the project in the simulator or device with iOS 5 and tap the first cell to expand it. Then tap the cell again to collapse it. Finally, tap the cell directly underneath it. The previous one disappears.
若要重新创建问题,请使用ios5在模拟器或设备中运行项目,并单击第一个单元来展开它。然后再轻敲细胞以使其崩溃。最后,点击下面的单元格。上一个就消失了。
Continuing the tapping for each cell in the section will cause all the cells to disappear, to where the entire section is missing.
继续为节中的每个单元格敲击将导致所有单元格消失,直到整个节消失。
I’ve also tried using reloadData instead of the current setup, but that ruins the animations and feels a bit like a hack anyway. reloadRowsAtIndexPaths should work, but the question is why doesn’t it?
我也尝试过使用reloadData而不是当前的设置,但是这会破坏动画,而且感觉有点像黑客。reloadRowsAtIndexPaths应该能行,但问题是它为什么不能行?
See images of what's happening below:
请看下面发生的事情的图片:
Table appears:
表显示:
Cell expands:
细胞扩展:
Cell collapses:
细胞崩溃:
Cell disappears (when tapping the cell underneath):
细胞消失(点击下方的细胞):
Keep repeating until the entire section disappears:
不断重复,直到整个部分消失:
EDIT: Overriding the alpha is a hack, but works. Here is another 'hack' that fixes it as well but WHY does it fix it?
编辑:重写alpha是一种技巧,但是很有用。这里还有一个“hack”,它也可以修复它,但是为什么要修复它呢?
JVViewController.m line 125:
JVViewController。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]];
}
EDIT 2:
编辑2:
Made a few minor changes to demo project, worth checking out and reviewing JVViewController didSelectRowAtIndexPath method.
对demo项目做了一些小小的修改,值得检查和检查JVViewController didSelectRowAtIndexPath方法。
12 个解决方案
#1
20
Your problem is in setExpanded: in JVCell.m, you are directly editing the frame of the target cell in that method.
问题出现在setexpand中: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;
}
Update it to:
更新:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Then remove the call to -reloadRowsAtIndexPaths:withRowAnimation:
at line 163 of JVViewController.m and it will animate as expected.
然后删除对JVViewController的第163行-reloadRowsAtIndexPaths:withRowAnimation:的调用。它会像预期的那样充满活力。
-reloadRowsAtIndexPaths:withRowAnimation:
expects different cells to be returned for the provided indexPaths. Since you are only adjusting sizes -beginUpdates
& -endUpdates
is sufficient to layout the table view cells again.
-reloadRowsAtIndexPaths:withRowAnimation:期望为提供的索引路径返回不同的单元格。由于您只是在调整大小,所以-beginUpdates & - aled就足以再次布局表视图单元格了。
#2
7
May be I am missing a point, but why dont you just use:
也许我漏掉了一点,但你为什么不直接用:
UITableViewRowAnimationNone
UITableViewRowAnimationNone
I mean instead of :
我的意思不是:
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
use
使用
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
#3
7
To animate the height changes of a tableView just call.
要使表视图的高度变化具有动画效果,只需调用。
[tableView beginUpdates];
[tableView endUpdates];
Don't call reloadRowsAtIndexPaths:
别叫reloadRowsAtIndexPaths:
See Can you animate a height change on a UITableViewCell when selected?
你能在UITableViewCell被选中时激活一个高度变化吗?
#4
5
The cell that is fading out is the previous cell that is not changing size. As the documentation of reloadRowsAtIndexPaths:withRowAnimation:
states:
正在淡出的单元格是没有改变大小的前一个单元格。作为reloadRowsAtIndexPaths:withRowAnimation: states的文档:
The table animates that new cell in as it animates the old row out.
该表在显示旧行时对新单元格进行动画处理。
What happens is the opacity is set to 1 then immediately set to 0 and so it fades out.
不透明度设置为1然后立即设置为0,然后淡出。
If both the previous and the new cell change size then it works as intended. This is because the begin/end Updates notice the height changes and create new animations on those cells overriding the reloadRowsAtIndexPaths:withRowAnimation:
ones.
如果前一个单元格和新单元格都改变了大小,那么它就会按照预期工作。这是因为开始/结束更新会注意到高度的变化,并在重写reloadRowsAtIndexPaths:withRowAnimation: ones的单元格上创建新的动画。
Your problem is due to abusing reloadRowsAtIndexPaths:withRowAnimation:
to resize the cells when it's intended for loading new cells.
您的问题是由于滥用reloadRowsAtIndexPaths:withRowAnimation:在加载新单元格时调整单元格的大小。
But you don't need reloadRowsAtIndexPaths:withRowAnimation:
at all. Just change the expanded state of the cells and do the begin/end updates. That will handle all the animation for you.
但你不需要reloadRowsAtIndexPaths:withRowAnimation:完全不需要。只需更改单元格的展开状态并进行开始/结束更新。这将为您处理所有的动画。
As a side note I found the blue selection a little annoying, in JVCell set the selectedBackgroundView to the same image as the backgroundView (or create a new image that has the correct look of a selected cell).
作为附加说明,我发现蓝色的选择有点烦人,在JVCell中,将selectedBackgroundView设置为与backgroundView相同的图像(或者创建一个新图像,它具有选定单元格的正确外观)。
EDIT:
编辑:
Move the statement adding previousIndexPath_
to indicesToReload
to the if statement (at line 132) so that it is only added if the previous cell was expanded and needs to resize.
将向if语句(在第132行)添加previousIndexPath_的语句移动到indicesToReload,以便只有在扩展之前的单元格并需要调整大小时才添加它。
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
This removes the case where the previous collapsed cell would disappear.
这将删除先前崩溃的单元格将消失的情况。
Another option would be to set previousIndexPath_
to nil when the current cell is collapsed and only set it when a cell expands.
另一种选择是在当前单元格折叠时将previousIndexPath_设置为nil,只在单元格展开时设置。
This still feels like a hack. Doing both the reloadRows and the begin/end Updates causes the tableView to reload everything twice but both seem to be needed to animate correctly. I suppose if the table is not too large this won't be a performance problem.
这仍然让人觉得像个黑客。同时执行reloadRows和begin/end更新将导致tableView重新加载所有内容两次,但似乎都需要正确地进行动画操作。我认为如果表不是太大,这就不会成为性能问题。
#5
2
Short, pragmatic answer: Changing UITableViewRowAnimationAutomatic
to UITableViewRowAnimationTop
solves the issue. No more disappearing rows! (tested on iOS 5.1)
简短、实用的回答:将UITableViewRowAnimationAutomatic改成UITableViewRowAnimationTop可以解决这个问题。不再消失行!在iOS 5.1(测试)
Another short, pragmatic answer, since UITableViewRowAnimationTop
is said to cause its own issues: Create a new cell view instead of modifying the existing one's frame. In a real app the data displayed in the cell view is supposed to be in the Model part of the app anyway, so if properly designed it shouldn't be a problem to create another cell view which displays the same data only in a different manner (frame in our case).
另一个简短、实用的回答,因为UITableViewRowAnimationTop据说会导致它自己的问题:创建一个新的单元视图,而不是修改现有的框架。在真实的应用程序中显示的数据模型中细胞的观点应该是应用程序的一部分,无论如何,如果设计得当它不应该成为一个问题创建另一个细胞视图只显示相同的数据以不同的方式(在我们的例子中帧)。
Some more thoughts regarding animating the reload of the same cell:
关于激活同一个单元格的重载,还有一些想法:
UITableViewRowAnimationAutomatic
seems to resolve to UITableViewRowAnimationFade
in some cases, which is when you see the cells fading away and disappearing. The new cell is supposed to fade in while the old one fades out. But here the old cell and the new one are one and the same - So, could this even work? In the Core Animation level, Is it possible to fade out a view AND fade it in at the same time? Sounds dubious. So the result is that you just see the fade out. This could be considered an Apple bug, since an expected behavior could be that if the same view has changed, the alpha property wouldn't be animated (since it can't animate both to 0 and to 1 at the same time), but instead just the frame, color etc. would be animated.
UITableViewRowAnimationAutomatic似乎会在某些情况下决定uitableviewrowanimationdisappear,也就是当你看到细胞消失和消失的时候。新的细胞应该在旧细胞消失时逐渐消失。但是这里的旧单元格和新单元格是一样的-那么,这个能行吗?在核心动画级别,是否有可能将一个视图淡出并同时将其淡入?听起来可疑。结果就是你只看到淡出。这可以被认为是一个Apple bug,因为预期的行为可能是,如果相同的视图发生了更改,那么alpha属性将不会被动画化(因为它不能同时动画到0和1),而是只动画到帧,颜色等等。
Note the problem is just in the animation's display - if you scroll away and back, everything will appear correctly.
注意,问题只是在动画的显示中——如果你来回滚动,所有东西都会正确显示。
In iOS 4.3 the Automatic
mode might have been resolved to something other than Fade
which is why things work there (as you write they do) - I didn't dig into that.
在ios4.3中,自动模式可能已经被解析为除了淡出之外的其他东西,这就是为什么在那里工作(正如你写的那样)——我没有深入研究这个问题。
I don't know why iOS chooses the Fade
mode when it does. But one of the cases it does is when your code asks reloads a previously tapped cell, which is collapsed, and is different than the current tapped cell. Note the previously tapped cell is always reloaded, this line in your code is always called:
我不知道为什么iOS会选择渐变模式。但是它所做的一种情况是,当您的代码请求重新加载以前被选中的单元时,它会崩溃,并且与当前被选中的单元不同。注意,以前被选中的单元格总是被重新加载,代码中的这一行总是被调用:
[indicesToReload addObject:[previousIndexPath_ copy]];
This explains the magic disappearing cells scenario you have described.
这解释了您所描述的神奇消失的单元格场景。
By the way, the beginUpdates/endUpdates seem like a hack to me. This pair of calls is just supposed to contain animations, and there aren't any animations you are adding in addition to the rows you already asked to reload. All it did in this case is magically cause the Automatic
mode to not choose Fade
in some cases - But this just obscured the problem.
顺便说一下,那些初学者更新对我来说就像一个黑客。这对调用应该只包含动画,并且除了已经要求重新加载的行之外,不添加任何动画。在这种情况下,它所做的就是神奇地使自动模式在某些情况下不会选择渐变——但这只是模糊了问题。
A final note: I played around with the Top
mode and found it can also cause problems. For example plugging the following code makes cells disappear funkily:
最后一个注意事项:我使用了Top模式,发现它也会引起问题。例如,插入以下代码会使单元格有趣地消失:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Not sure if there is a real issue here (similar to the one with fading a view in and out at the same time), or maybe an Apple bug.
不确定这里是否存在真正的问题(类似于同时淡入和淡出视图的问题),或者可能是苹果的bug。
#6
1
I just downloaded your project & found this section of code in didSelectRowAtIndexPath
delegate where reloadRowsAtIndexPaths
is used.
我刚刚下载了您的项目,并在didSelectRowAtIndexPath委托中找到了使用reloadRowsAtIndexPaths的部分代码。
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
instead of the above why don't you try this?
你为什么不试试这个呢?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
The reason i am suggesting this is that I believe reloadRowsAtIndexPaths:...
only works when wrapped inbetween calls to:
我之所以这么说,是因为我相信reloadRowsAtIndexPaths:……只在调用之间进行包装时有效:
- (void)beginUpdates;
- (void)endUpdates;
Outside of that, behavior is undefined and as you've discovered, fairly unreliable. Quoting relevant part of "Table View Programming Guide for iPhone OS":
除此之外,行为是没有定义的,正如你所发现的,相当不可靠。引用“iPhone OS的表格视图编程指南”的相关部分:
To animate a batch insertion and deletion of rows and sections, call the insertion and deletion methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion and deletion methods within this block, row and section indexes may be invalid. beginUpdates...endUpdates blocks are not nestable.
要使批量插入和删除行和节动画,可以在一个动画块中调用插入和删除方法,这是由连续调用开始更新和结束定义的。如果不调用此块中的插入和删除方法,则行和节索引可能无效。beginUpdates……耐久的方块不是固定的。
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
在块的结尾,也就是保存数据返回之后,表视图查询它的数据源并像往常一样委托行和节数据。因此,支持表视图的集合对象应该更新以反映新的或删除的行或节。
The reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods, which were introduced in iPhone OS 3.0, are related to the methods discussed above. They allow you to request the table view to reload the data for specific sections and rows instead of loading the entire visible table view by calling reloadData.
reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods,是在iPhone OS 3.0中引入的,与上面讨论的方法有关。它们允许您请求表视图为特定的部分和行重新加载数据,而不是通过调用reloadData加载整个可见表视图。
There could be other valid reason but let me mull on this a bit, since i have your code too I could muck around with it. Hopefully we should figure it out...
可能还有其他有效的原因,但让我仔细考虑一下,因为我也有你的代码,我可以把它搞乱。希望我们能弄清楚……
#7
1
When you use this method, you have to be sure that you are on the main thread. Refreshing a UITableViewCell as follow should do the trick :
当您使用此方法时,您必须确保您处于主线程上。刷新一个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
0
@Javy, i noticed some wierd behavior while testing your app.
@Javy,我在测试你的应用时发现了一些奇怪的行为。
While running on iPhone 5.0 simulator the previousIndexpth_ variable is of class NSArray (it looks like.) here is the debugger output
当在iPhone 5.0模拟器上运行时,previousIndexpth_变量是类NSArray(它看起来是这样的),这里是调试器输出。
(lldb) po previousIndexPath_ (NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
(lldb) po previousIndexPath_ (NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
,
,
,
,
)
)
(lldb) po [previousIndexPath_ class]
(lldb)po(previousIndexPath_类)
(id) $7 = 0x0145cb64 __NSArrayI
(id) $7 = 0x0145cb64 __NSArrayI
Whereas in iPhone 4.3 simulator it is of type NSIndexPath.
而在iPhone 4.3模拟器中,它是NSIndexPath类型。
lldb) po [previousIndexPath_ class]
)lldb po(previousIndexPath_类)
(id) $5 = 0x009605c8 NSIndexPath
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
阿宝previousIndexPath_(lldb)
(NSIndexPath *) $6 = 0x04b5a570 2 indexes [0, 0]
(NSIndexPath *) $6 = 0x04b5a570 2个索引[0,0]
Are you aware of this issue? Not sure whether this will help but thought of letting you know.
你知道这个问题吗?我不确定这是否会有帮助,但我想让你知道。
#9
0
I think you should not retain the previous indexPath when the cell is not expanded, try by modifying you did select method like the below, its working fine..
我认为当单元格不展开时,不应该保留之前的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];
}
#10
0
This problem is caused by returning cached cells in cellForRowAtIndexPath. The reloadRowsAtIndexPaths is expecting to get fresh new cells from cellForRowAtIndexPath. If you do that you will be ok ... no workarounds required.
这个问题是由在cellForRowAtIndexPath中返回缓存的单元格引起的。reloadRowsAtIndexPaths期望从cellForRowAtIndexPath获得新的单元。如果你那样做,你会没事的……不需要变通方案。
From Apple doc: "Reloading a row causes the table view to ask its data source for a new cell for that row."
来自Apple doc:“重载一个行会导致表视图请求它的数据源为该行提供一个新的单元格。”
#11
0
I had a similar issue where I wanted to expand a cell when a switch is activated to display and extra label and button in the cell that is normally hidden when the cell is at its default height (44). I tried various versions of reloadRowsAtPath to no avail. Finally I decided to keep it simpler by adding a condition at heightForRowAtIndexPath like so:
我也遇到过类似的问题,当一个开关被激活以显示时,我想要扩展一个单元格;当单元格处于默认高度(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
}
And in whatever code you want to trigger the expanding of the cell just insert tableview.reloadData(). In my case it was when a switch was turned on to indicate the desire to reset a password.
在任何想要触发单元格扩展的代码中,只需插入tableview.reloadData()。在我的例子中,当一个开关被打开时,表明需要重新设置密码。
@IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
There is no lag with this approach, no visible way to see that the table reloaded and the expansion of the sell is done gradually like you'd expect. Hope this helps someone.
这种方法没有延迟,没有明显的方法可以看到表重新加载,而销售的扩展就像您预期的那样逐渐完成。希望这可以帮助别人。
#1
20
Your problem is in setExpanded: in JVCell.m, you are directly editing the frame of the target cell in that method.
问题出现在setexpand中: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;
}
Update it to:
更新:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Then remove the call to -reloadRowsAtIndexPaths:withRowAnimation:
at line 163 of JVViewController.m and it will animate as expected.
然后删除对JVViewController的第163行-reloadRowsAtIndexPaths:withRowAnimation:的调用。它会像预期的那样充满活力。
-reloadRowsAtIndexPaths:withRowAnimation:
expects different cells to be returned for the provided indexPaths. Since you are only adjusting sizes -beginUpdates
& -endUpdates
is sufficient to layout the table view cells again.
-reloadRowsAtIndexPaths:withRowAnimation:期望为提供的索引路径返回不同的单元格。由于您只是在调整大小,所以-beginUpdates & - aled就足以再次布局表视图单元格了。
#2
7
May be I am missing a point, but why dont you just use:
也许我漏掉了一点,但你为什么不直接用:
UITableViewRowAnimationNone
UITableViewRowAnimationNone
I mean instead of :
我的意思不是:
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
use
使用
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
#3
7
To animate the height changes of a tableView just call.
要使表视图的高度变化具有动画效果,只需调用。
[tableView beginUpdates];
[tableView endUpdates];
Don't call reloadRowsAtIndexPaths:
别叫reloadRowsAtIndexPaths:
See Can you animate a height change on a UITableViewCell when selected?
你能在UITableViewCell被选中时激活一个高度变化吗?
#4
5
The cell that is fading out is the previous cell that is not changing size. As the documentation of reloadRowsAtIndexPaths:withRowAnimation:
states:
正在淡出的单元格是没有改变大小的前一个单元格。作为reloadRowsAtIndexPaths:withRowAnimation: states的文档:
The table animates that new cell in as it animates the old row out.
该表在显示旧行时对新单元格进行动画处理。
What happens is the opacity is set to 1 then immediately set to 0 and so it fades out.
不透明度设置为1然后立即设置为0,然后淡出。
If both the previous and the new cell change size then it works as intended. This is because the begin/end Updates notice the height changes and create new animations on those cells overriding the reloadRowsAtIndexPaths:withRowAnimation:
ones.
如果前一个单元格和新单元格都改变了大小,那么它就会按照预期工作。这是因为开始/结束更新会注意到高度的变化,并在重写reloadRowsAtIndexPaths:withRowAnimation: ones的单元格上创建新的动画。
Your problem is due to abusing reloadRowsAtIndexPaths:withRowAnimation:
to resize the cells when it's intended for loading new cells.
您的问题是由于滥用reloadRowsAtIndexPaths:withRowAnimation:在加载新单元格时调整单元格的大小。
But you don't need reloadRowsAtIndexPaths:withRowAnimation:
at all. Just change the expanded state of the cells and do the begin/end updates. That will handle all the animation for you.
但你不需要reloadRowsAtIndexPaths:withRowAnimation:完全不需要。只需更改单元格的展开状态并进行开始/结束更新。这将为您处理所有的动画。
As a side note I found the blue selection a little annoying, in JVCell set the selectedBackgroundView to the same image as the backgroundView (or create a new image that has the correct look of a selected cell).
作为附加说明,我发现蓝色的选择有点烦人,在JVCell中,将selectedBackgroundView设置为与backgroundView相同的图像(或者创建一个新图像,它具有选定单元格的正确外观)。
EDIT:
编辑:
Move the statement adding previousIndexPath_
to indicesToReload
to the if statement (at line 132) so that it is only added if the previous cell was expanded and needs to resize.
将向if语句(在第132行)添加previousIndexPath_的语句移动到indicesToReload,以便只有在扩展之前的单元格并需要调整大小时才添加它。
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
This removes the case where the previous collapsed cell would disappear.
这将删除先前崩溃的单元格将消失的情况。
Another option would be to set previousIndexPath_
to nil when the current cell is collapsed and only set it when a cell expands.
另一种选择是在当前单元格折叠时将previousIndexPath_设置为nil,只在单元格展开时设置。
This still feels like a hack. Doing both the reloadRows and the begin/end Updates causes the tableView to reload everything twice but both seem to be needed to animate correctly. I suppose if the table is not too large this won't be a performance problem.
这仍然让人觉得像个黑客。同时执行reloadRows和begin/end更新将导致tableView重新加载所有内容两次,但似乎都需要正确地进行动画操作。我认为如果表不是太大,这就不会成为性能问题。
#5
2
Short, pragmatic answer: Changing UITableViewRowAnimationAutomatic
to UITableViewRowAnimationTop
solves the issue. No more disappearing rows! (tested on iOS 5.1)
简短、实用的回答:将UITableViewRowAnimationAutomatic改成UITableViewRowAnimationTop可以解决这个问题。不再消失行!在iOS 5.1(测试)
Another short, pragmatic answer, since UITableViewRowAnimationTop
is said to cause its own issues: Create a new cell view instead of modifying the existing one's frame. In a real app the data displayed in the cell view is supposed to be in the Model part of the app anyway, so if properly designed it shouldn't be a problem to create another cell view which displays the same data only in a different manner (frame in our case).
另一个简短、实用的回答,因为UITableViewRowAnimationTop据说会导致它自己的问题:创建一个新的单元视图,而不是修改现有的框架。在真实的应用程序中显示的数据模型中细胞的观点应该是应用程序的一部分,无论如何,如果设计得当它不应该成为一个问题创建另一个细胞视图只显示相同的数据以不同的方式(在我们的例子中帧)。
Some more thoughts regarding animating the reload of the same cell:
关于激活同一个单元格的重载,还有一些想法:
UITableViewRowAnimationAutomatic
seems to resolve to UITableViewRowAnimationFade
in some cases, which is when you see the cells fading away and disappearing. The new cell is supposed to fade in while the old one fades out. But here the old cell and the new one are one and the same - So, could this even work? In the Core Animation level, Is it possible to fade out a view AND fade it in at the same time? Sounds dubious. So the result is that you just see the fade out. This could be considered an Apple bug, since an expected behavior could be that if the same view has changed, the alpha property wouldn't be animated (since it can't animate both to 0 and to 1 at the same time), but instead just the frame, color etc. would be animated.
UITableViewRowAnimationAutomatic似乎会在某些情况下决定uitableviewrowanimationdisappear,也就是当你看到细胞消失和消失的时候。新的细胞应该在旧细胞消失时逐渐消失。但是这里的旧单元格和新单元格是一样的-那么,这个能行吗?在核心动画级别,是否有可能将一个视图淡出并同时将其淡入?听起来可疑。结果就是你只看到淡出。这可以被认为是一个Apple bug,因为预期的行为可能是,如果相同的视图发生了更改,那么alpha属性将不会被动画化(因为它不能同时动画到0和1),而是只动画到帧,颜色等等。
Note the problem is just in the animation's display - if you scroll away and back, everything will appear correctly.
注意,问题只是在动画的显示中——如果你来回滚动,所有东西都会正确显示。
In iOS 4.3 the Automatic
mode might have been resolved to something other than Fade
which is why things work there (as you write they do) - I didn't dig into that.
在ios4.3中,自动模式可能已经被解析为除了淡出之外的其他东西,这就是为什么在那里工作(正如你写的那样)——我没有深入研究这个问题。
I don't know why iOS chooses the Fade
mode when it does. But one of the cases it does is when your code asks reloads a previously tapped cell, which is collapsed, and is different than the current tapped cell. Note the previously tapped cell is always reloaded, this line in your code is always called:
我不知道为什么iOS会选择渐变模式。但是它所做的一种情况是,当您的代码请求重新加载以前被选中的单元时,它会崩溃,并且与当前被选中的单元不同。注意,以前被选中的单元格总是被重新加载,代码中的这一行总是被调用:
[indicesToReload addObject:[previousIndexPath_ copy]];
This explains the magic disappearing cells scenario you have described.
这解释了您所描述的神奇消失的单元格场景。
By the way, the beginUpdates/endUpdates seem like a hack to me. This pair of calls is just supposed to contain animations, and there aren't any animations you are adding in addition to the rows you already asked to reload. All it did in this case is magically cause the Automatic
mode to not choose Fade
in some cases - But this just obscured the problem.
顺便说一下,那些初学者更新对我来说就像一个黑客。这对调用应该只包含动画,并且除了已经要求重新加载的行之外,不添加任何动画。在这种情况下,它所做的就是神奇地使自动模式在某些情况下不会选择渐变——但这只是模糊了问题。
A final note: I played around with the Top
mode and found it can also cause problems. For example plugging the following code makes cells disappear funkily:
最后一个注意事项:我使用了Top模式,发现它也会引起问题。例如,插入以下代码会使单元格有趣地消失:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Not sure if there is a real issue here (similar to the one with fading a view in and out at the same time), or maybe an Apple bug.
不确定这里是否存在真正的问题(类似于同时淡入和淡出视图的问题),或者可能是苹果的bug。
#6
1
I just downloaded your project & found this section of code in didSelectRowAtIndexPath
delegate where reloadRowsAtIndexPaths
is used.
我刚刚下载了您的项目,并在didSelectRowAtIndexPath委托中找到了使用reloadRowsAtIndexPaths的部分代码。
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
instead of the above why don't you try this?
你为什么不试试这个呢?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
The reason i am suggesting this is that I believe reloadRowsAtIndexPaths:...
only works when wrapped inbetween calls to:
我之所以这么说,是因为我相信reloadRowsAtIndexPaths:……只在调用之间进行包装时有效:
- (void)beginUpdates;
- (void)endUpdates;
Outside of that, behavior is undefined and as you've discovered, fairly unreliable. Quoting relevant part of "Table View Programming Guide for iPhone OS":
除此之外,行为是没有定义的,正如你所发现的,相当不可靠。引用“iPhone OS的表格视图编程指南”的相关部分:
To animate a batch insertion and deletion of rows and sections, call the insertion and deletion methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion and deletion methods within this block, row and section indexes may be invalid. beginUpdates...endUpdates blocks are not nestable.
要使批量插入和删除行和节动画,可以在一个动画块中调用插入和删除方法,这是由连续调用开始更新和结束定义的。如果不调用此块中的插入和删除方法,则行和节索引可能无效。beginUpdates……耐久的方块不是固定的。
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
在块的结尾,也就是保存数据返回之后,表视图查询它的数据源并像往常一样委托行和节数据。因此,支持表视图的集合对象应该更新以反映新的或删除的行或节。
The reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods, which were introduced in iPhone OS 3.0, are related to the methods discussed above. They allow you to request the table view to reload the data for specific sections and rows instead of loading the entire visible table view by calling reloadData.
reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods,是在iPhone OS 3.0中引入的,与上面讨论的方法有关。它们允许您请求表视图为特定的部分和行重新加载数据,而不是通过调用reloadData加载整个可见表视图。
There could be other valid reason but let me mull on this a bit, since i have your code too I could muck around with it. Hopefully we should figure it out...
可能还有其他有效的原因,但让我仔细考虑一下,因为我也有你的代码,我可以把它搞乱。希望我们能弄清楚……
#7
1
When you use this method, you have to be sure that you are on the main thread. Refreshing a UITableViewCell as follow should do the trick :
当您使用此方法时,您必须确保您处于主线程上。刷新一个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
0
@Javy, i noticed some wierd behavior while testing your app.
@Javy,我在测试你的应用时发现了一些奇怪的行为。
While running on iPhone 5.0 simulator the previousIndexpth_ variable is of class NSArray (it looks like.) here is the debugger output
当在iPhone 5.0模拟器上运行时,previousIndexpth_变量是类NSArray(它看起来是这样的),这里是调试器输出。
(lldb) po previousIndexPath_ (NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
(lldb) po previousIndexPath_ (NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
,
,
,
,
)
)
(lldb) po [previousIndexPath_ class]
(lldb)po(previousIndexPath_类)
(id) $7 = 0x0145cb64 __NSArrayI
(id) $7 = 0x0145cb64 __NSArrayI
Whereas in iPhone 4.3 simulator it is of type NSIndexPath.
而在iPhone 4.3模拟器中,它是NSIndexPath类型。
lldb) po [previousIndexPath_ class]
)lldb po(previousIndexPath_类)
(id) $5 = 0x009605c8 NSIndexPath
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
阿宝previousIndexPath_(lldb)
(NSIndexPath *) $6 = 0x04b5a570 2 indexes [0, 0]
(NSIndexPath *) $6 = 0x04b5a570 2个索引[0,0]
Are you aware of this issue? Not sure whether this will help but thought of letting you know.
你知道这个问题吗?我不确定这是否会有帮助,但我想让你知道。
#9
0
I think you should not retain the previous indexPath when the cell is not expanded, try by modifying you did select method like the below, its working fine..
我认为当单元格不展开时,不应该保留之前的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];
}
#10
0
This problem is caused by returning cached cells in cellForRowAtIndexPath. The reloadRowsAtIndexPaths is expecting to get fresh new cells from cellForRowAtIndexPath. If you do that you will be ok ... no workarounds required.
这个问题是由在cellForRowAtIndexPath中返回缓存的单元格引起的。reloadRowsAtIndexPaths期望从cellForRowAtIndexPath获得新的单元。如果你那样做,你会没事的……不需要变通方案。
From Apple doc: "Reloading a row causes the table view to ask its data source for a new cell for that row."
来自Apple doc:“重载一个行会导致表视图请求它的数据源为该行提供一个新的单元格。”
#11
0
I had a similar issue where I wanted to expand a cell when a switch is activated to display and extra label and button in the cell that is normally hidden when the cell is at its default height (44). I tried various versions of reloadRowsAtPath to no avail. Finally I decided to keep it simpler by adding a condition at heightForRowAtIndexPath like so:
我也遇到过类似的问题,当一个开关被激活以显示时,我想要扩展一个单元格;当单元格处于默认高度(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
}
And in whatever code you want to trigger the expanding of the cell just insert tableview.reloadData(). In my case it was when a switch was turned on to indicate the desire to reset a password.
在任何想要触发单元格扩展的代码中,只需插入tableview.reloadData()。在我的例子中,当一个开关被打开时,表明需要重新设置密码。
@IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
There is no lag with this approach, no visible way to see that the table reloaded and the expansion of the sell is done gradually like you'd expect. Hope this helps someone.
这种方法没有延迟,没有明显的方法可以看到表重新加载,而销售的扩展就像您预期的那样逐渐完成。希望这可以帮助别人。