删除 UITableView 的部分后,标题视图的框架永远改变
Posted
技术标签:
【中文标题】删除 UITableView 的部分后,标题视图的框架永远改变【英文标题】:Frame of header view changed forever after deletion of UITableView's section 【发布时间】:2013-06-07 08:06:02 【问题描述】:我正在重用标题,将它们保存在字典中,其中节号是键,标题视图是值。
在我动画删除一个部分并重新加载另一个部分后,只有这些部分的标题视图“消失”。经过一番搜索,我发现这些标题视图的框架已更改。 Frame.X
的标题视图从被UITableViewRowAnimationRight
移动器删除的部分向右移动了屏幕宽度 (320),而用UITableViewRowAnimationAutomatic
重新加载的部分被Y
移动了。
主要问题和困惑来自于我无法更改这些框架!嗯,一开始它们会发生变化,但在视觉上没有任何变化,而在另一个调用 viewForHeaderInSection:
时,帧会再次移动。
这是我的做法:
假设我们有 3 个部分,每个部分的数据位于数组 _first
、_second
和 _third
之一中。通过按钮的 touchupinside 我们调用handleTouchUp:
。 (_second
保持不变)
- (void) handleTouchUp: (UIButton *)sender
if ([_first count] == 0)
sectionCount = 3;
_first = [NSMutableArray arrayWithObjects: @"First 1", @"First 2", nil];
_third = [NSMutableArray arrayWithObjects: @"Third 1", @"Third 2", @"Third 3", @"Third 4", @"Third 5", nil];
[tableView reloadData];
else
sectionCount = 2;
[_first removeAllObjects];
_third = [NSMutableArray arrayWithObjects: @"First 1", @"First 2", @"Third 1", @"Third 2",
@"Third 3", @"Third 4", @"Third 5", nil];
[tableView beginUpdates];
[tableView deleteSections: [NSIndexSet indexSetWithIndex: 0] withRowAnimation:UITableViewRowAnimationRight];
[tableView reloadSections: [NSIndexSet indexSetWithIndex: 2] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
NSNumber *key = [NSNumber numberWithInteger: section];
UIView *header = [_headers objectForKey: key];
if (header)
else
header = [[UIView alloc] init];
switch (section)
//Doing something with header here (e.g. set background color)
[_headers setObject:header forKey: key];
[header setFrame: CGRectMake(0, 0, 320, 25)];
return header;
Here is full code of ViewController.m 和test project
更新: 项目中可能还有其他一些错误,但这并不重要——它是测试项目。唯一重要的问题是无效的标题视图。删除无效视图并创建新视图不是解决方案——它扼杀了重用标题视图的意义。
【问题讨论】:
【参考方案1】:我在您的项目中发现了几个错误,这些错误实际上导致了一些崩溃,我相信它们也是您在应用程序中看到的标题视图错误的原因。 在我修复了这些之后,应用程序对我来说似乎可以正常运行。
这是您项目的更新版本:https://bitbucket.org/reydan/tableheaderviews
我试图让它尽可能与您最初的项目相似,但我不得不进行一些更改。如果您认为它与您想要的功能不匹配,请发表评论,我会更新(这是我推断项目应该表现的样子) 下面解释的所有更改也可以在这个更新的项目中看到。
我发现的问题与这样一个事实有关,即如果您从表视图中删除一个部分,则该部分之后的所有部分的索引都会更改(带有-1)。这是正常的,因为节索引实际上是一个索引,而不是分配给每个节的唯一键。
删除第一部分后,第三部分现在的 index = 1 并且表视图委托方法读取数据不正确(包括标题视图),因为您拥有基于部分索引的所有内容。
为了解决这个问题,我更改了标题视图缓存以使用数据数组作为键(_first、_second 或 _third)。 为了使这更容易,我创建了一个可以从多个位置调用并根据节索引和当前节计数返回正确数据数组的方法:
- (NSArray*)dataArrayForSection:(NSInteger)section
if (sectionCount == 3)
switch (section)
case 0: return _first;
case 1: return _second;
case 2: return _third;
else
switch (section)
case 0: return _second;
case 1: return _third;
return nil;
现在头缓存使用数据数组作为键,在handleTouchUp
方法中重新分配数组时删除头条目很重要。
- (void) handleTouchUp: (UIButton *)sender
NSLog(@"Touched!");
if ([_first count] == 0)
sectionCount = 3;
// **INSERTED** Removes the cache for the _first and _third keys because you are recreating the arrays again,
// and so you are losing the initial key. Not doing this, results in a memory leak
[_headers removeObjectForKey:_first];
[_headers removeObjectForKey:_third];
_first = [NSMutableArray arrayWithObjects: @"First 1", @"First 2", nil];
_third = [NSMutableArray arrayWithObjects: @"Third 1", @"Third 2", @"Third 3", @"Third 4", @"Third 5", nil];
[tableView reloadData];
// [tableView beginUpdates];
// [tableView reloadSections: [NSIndexSet indexSetWithIndex: 0] withRowAnimation:UITableViewRowAnimationRight];
// [tableView reloadSections: [NSIndexSet indexSetWithIndex: 2] withRowAnimation:UITableViewRowAnimationAutomatic];
// [tableView endUpdates];
else
我改进的另一件事是重用表格视图单元格。您应该始终尝试并重用现有的 UITableViewCells(除了少数例外)。 cellForRowAtIndexPath
现在看起来像这样:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSArray* dataarray = [self dataArrayForSection:indexPath.section];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"my_text_cell"];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"my_text_cell"];
if (dataarray == nil)
cell.textLabel.text = @"default";
else
cell.textLabel.text = [dataarray objectAtIndex:indexPath.row];
return cell;
如果您有任何问题,请发表评论。
编辑
我尝试按照您的要求更新我的测试项目,以保持缓存完好无损,并且我确实能够重现错误 - 在删除/重新加载动画之后,最后一部分的标题不可见。
我发现 ios 选择的 Automatic reload 动画是 Fade 动画。 这会导致标题视图淡出。我通过在应用程序中添加另一个按钮来测试这一点,该按钮为所有缓存的标题视图设置 alpha=1。重新加载动画后,我按下此按钮,标题视图正确显示。
我尝试使用其他动画,但它们看起来不太好。
最后,我认为 Apple 不建议缓存标头视图。在文档中,从 iOS 6.0 开始,有一种新方法可以解决此问题。它的行为类似于出列单元格,但用于页眉/页脚视图- (id)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier
因此,如果您的目标是 iOS 6.0+,那么您可以使用它而不是手动缓存标头。否则,我找到的解决方案是仅从缓存中删除已删除和重新加载部分(_first 和 _third)的标题视图。
我更新了项目以在刷新时仅删除最后一节标题
【讨论】:
重点是标题视图的框架在动画删除/重新加载后变得无效。所有其他错误(包括重用单元格)并不重要,因为它只是我编写的用于显示标题视图框架问题的测试项目。您的项目没有此问题的原因是您总是重新创建受动画影响的标题视图,而不是这样做是标题视图“缓存”的全部意义。此外,我没有收到您关于丢失初始密钥的评论(就在从缓存中删除标头之前)。 但是无论如何,标题视图不应该在动画之后被破坏,那是错误的。如果我想使用外观完全相同的视图,如果它没有损坏并且该视图在视图层次结构中显示一次,我应该能够使用完全相同的视图。如果可以,请尽量不要清除缓存,否则会导致视图损坏。 我会尝试更好地解释错误键的含义。您已经缓存了键 0、1、2 的标题视图。当您删除第一个部分 (index=0) 并刷新第 2 部分时,将使用 index=1 调用该刷新 - 因为第一个部分消失了,所有其他部分都以 -1 移动。因此,在该标题刷新中,根据您的代码,您将返回带有键 1 的标题视图,但该确切的 uiview 也用于前面的第二部分(未刷新,它保留了旧的标题视图)。我认为在 2 个地方拥有相同的 UIView 会导致问题。 我的项目可以在不刷新头缓存的情况下工作,但在这种情况下,您不需要重新分配 _first、_second 和 _third 数组。您可以更改它们的内容,但不能重新分配它们——因为它们被用作标题的键。如果你愿意,我可以更新我的项目以反映这一点。 虽然您的回答费了很大力气,但这不是一个解决方案:(以上是关于删除 UITableView 的部分后,标题视图的框架永远改变的主要内容,如果未能解决你的问题,请参考以下文章
UITableView - 核心数据应用程序中的表视图崩溃,因为删除后行数无效