重新排序单元格后重新加载自定义 UITableViewCell

Posted

技术标签:

【中文标题】重新排序单元格后重新加载自定义 UITableViewCell【英文标题】:Reloading custom UITableViewCell after reordering cells 【发布时间】:2010-11-21 10:18:39 【问题描述】:

我有UITableView,它使用自定义UITableViewCells。单元格可以具有三种类型的背景图像之一(在每个单元格的.backgroundView.image 属性中设置):顶部、中间或底部。顶部和底部图像用于第一个和最后一个单元格,并且具有圆角(很像分组的UITableView 的单元格)。 UITableView 中的所有其他“中间”单元格都有一个矩形中间背景图像。

我的问题是,在 UITableViewEdit 模式中重新排序单元格时,单元格不会根据其新位置刷新不同的背景图像。例如,如果我将第一个单元格移动到表格的中间,它仍然保留其原始的“顶部”背景图像(带有圆角),并且出现在表格视图顶部的新单元格仍然具有其原始的“中间”背景图片,看起来有点奇怪。

我可以通过在表格视图上执行reloadData 来解决此问题,但这存在无法为更改提供优美优美的动画的问题。我注意到,在重新排序标准分组 UITableView 时,只要移动/拖动单元格,相关单元格上的背景图像就会发生变化(甚至在移动的单元格被放到新位置之前),这看起来非常好。我也想实现同样的目标。

为此,我实现了tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath,每次在表格视图中拖动一行时都会调用它。在此我尝试了各种设置backgroundView.image 的方法,包括直接设置图像,然后还明确告诉它使用setNeedsLayoutsetNeedsDisplay 重绘单元格和/或图像,但似乎没有任何东西使单元格重绘。我有NSLog'ged 这些更改的结果,它们似乎正在提交到单元格,因为正确的值出现在 NSLog 中,但单元格似乎没有在屏幕上更新。

如果有人能指出我做错了什么,或者提出更好的方法来实现工作成果,我将不胜感激。谢谢!

编辑:以下代码从赏金中提取并格式化,以便更容易消化:

UIImage *rowBackground;
UIImage *selectionBackground;
NSInteger sectionRows = [_tableView numberOfRowsInSection:[indexPath section]];
NSInteger row = [indexPath row];
if (row == 0 && row == sectionRows - 1)

    rowBackground = [UIImage imageNamed:@"field_title_bkg.png"];
    selectionBackground = [UIImage imageNamed:@"field_title_bkg.png"];

else if (row == 0)

    rowBackground = [UIImage imageNamed:@"table_row_top_bkg.png"];
    selectionBackground = [UIImage imageNamed:@"table_row_top_bkg.png"];

else if (row == sectionRows - 1)

    rowBackground = [UIImage imageNamed:@"table_bottom_row_bkg.png"];
    selectionBackground = [UIImage imageNamed:@"table_bottom_row_bkg.png"];

else

    rowBackground = [UIImage imageNamed:@"table_row_bkg.png"];
    selectionBackground = [UIImage imageNamed:@"table_row_bkg.png"];

UIImageView *rowBackGroundImageView = [[UIImageView alloc] initWithImage:rowBackground];
UIImageView *rowBackGroundSelectionImageView = [[UIImageView alloc] initWithImage:selectionBackground];
if (row != sectionRows - 1)

    UIImageView *sep = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cell_separator.png"]];
    [sep setFrame:CGRectMake(20, 56, 280, 1)];
    [rowBackGroundImageView addSubview:sep];

[cell setBackgroundView:rowBackGroundImageView];
[rowBackGroundImageView release];

【问题讨论】:

您可以尝试在表格视图上使用另一个视图来掩盖表格视图。那么你就不需要细胞的背景图像了。 你能再解释一下这个想法吗?我不太了解它是如何工作的。 相关问题:***.com/questions/7338161/… 您的代表的- (void)tableView:willDisplayCell:forRowAtIndexPath: 是否被呼叫?我发现自定义背景最可靠的地方就是这种方法。 我认为这与仍在编辑的单元格有关。您可以尝试以下代码,但请注意,在后台绘制不是线程安全的,您根本不应该使用它 - 仅用于测试:[tableView performSelectorInBackground:@selector(reloadData) withObject:nil]; // or setNeedsDisplay: 【参考方案1】:

在您的项目中编写以下代码,它应该可以按预期工作。

- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
      toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath

    NSInteger sectionRows = [tableView numberOfRowsInSection:[sourceIndexPath section]];

    UITableViewCell *sourceCell = [tableView cellForRowAtIndexPath:sourceIndexPath];
    UITableViewCell *destCell = [tableView cellForRowAtIndexPath:proposedDestinationIndexPath];

    if(sourceIndexPath.row == 0 && proposedDestinationIndexPath.row == 1)
    
        ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_row_top_bkg.png"];

        if(proposedDestinationIndexPath.row == sectionRows - 1)
            ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_bottom_row_bkg.png"];
        else
            ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_row_bkg.png"];
    

    else if(sourceIndexPath.row == sectionRows - 1 && proposedDestinationIndexPath.row == sectionRows - 2)
    
        ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_bottom_row_bkg.png"];

        if(proposedDestinationIndexPath.row == 0)
            ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_row_top_bkg.png"];
        else
            ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_row_bkg.png"];
    

    else if(proposedDestinationIndexPath.row == 0)
    
        ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_row_top_bkg.png"];

        destCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:proposedDestinationIndexPath.section]];
        if(sectionRows == 2)
            ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_bottom_row_bkg.png"];
        else
            ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_row_bkg.png"];
    

    else if(proposedDestinationIndexPath.row == sectionRows - 1)
    
        ((UIImageView *)sourceCell.backgroundView).image = [UIImage imageNamed:@"table_bottom_row_bkg.png"];

        destCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:sectionRows - 1 inSection:proposedDestinationIndexPath.section]];
        if(sectionRows == 2)
            ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_row_top_bkg.png"];
        else
            ((UIImageView *)destCell.backgroundView).image = [UIImage imageNamed:@"table_row_bkg.png"];
    

    return proposedDestinationIndexPath;

【讨论】:

这很完美!我的附件视图似乎消失了,但我还是想定制一个。 因此,如果您将一行拖到不同的行,然后在放开拖动之前决定不希望它在那里,图像将更改为错误的单元格图像:/【参考方案2】:

如果您只想重新加载几个单元格而不是整个表格,您可以使用:

reloadRowsAtIndexPaths:withRowAnimation:

可作为 UITableView 的实例方法使用。

【讨论】:

嗨罗伯特。试过了,但它在 tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath 方法中不起作用(即,当行被拖动/移动时)并崩溃并显示动画后的行数和节数必须与动画前的数字相匹配的消息。考虑到当我们尝试调用此方法时行仍在移动,这并不奇怪。 您是否尝试过在 UITableViewDataSource 方法tableView:moveRowAtIndexPath:toIndexPath: 中执行重新加载?我认为移动完成后会调用一个。 嗨罗伯特。抱歉回复晚了。我也试过这个,但它会导致一些奇怪的行为。如果我只重新加载toIndexPath,它将使fromIndexPath 处的行完全消失。如果我重新加载两个索引路径,它真的会变得混乱并开始将多个单元格副本放在彼此之上!【参考方案3】:

试试这个:

[tableView beginUpdates];
[tableView endUpdates];

【讨论】:

【参考方案4】:

我自己没有尝试过,但为什么不在 cellForRowAtIndexPath 方法上测试您的 indexPath.row 值,并为第一行和最后一行提供不同的 backgroundView.image 值?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

我希望这会有所帮助。 干杯, 罗格

【讨论】:

嗨,罗格。这就是我目前正在做的事情,但是当行移动时不会调用cellForRowAtIndexPath,因为该行已经在屏幕上。我尝试在tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath 方法中手动调用cellForRowAtIndexPath,但没有任何反应(即单元格不会在屏幕上重新加载/重绘)。【参考方案5】:

(this solution only works for the reordering of rows within the same section. It'll fail when dragging and dropping rows to a different section)

我在重新排序后更新单元格的详细标签文本时遇到了同样的问题。

我用下面的代码解决了:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath  toIndexPath:(NSIndexPath *)toIndexPath 

UITableViewCell* cell = [self.table cellForRowAtIndexPath:fromIndexPath]; 
cell.detailTextLabel.text = @"NEW STRING";

请注意,您必须更新 fromIndexPath 处的单元格,因为在此方法返回之前,tableview 的内部状态似乎不会更新。如果您在toIndexPath 获得单元格,则在重新排序完成后,您将看到相邻单元格得到更新。 (顶部或底部)。

【讨论】:

【参考方案6】:

用于在 ios sdk 中部分重新加载行的 api 无法正常工作(这就是为什么人们在此处关注此帖子)

在重新排序动画完成后,我最终使用计时器重新加载整个数据。 它对我有用。

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 

    //things to be done when reordering rows
    ....


    //issue a timer to reload data
    NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval: 0.1
                                             target: self
                                           selector:@selector(updateRow)
                                           userInfo: nil repeats:NO];





-(void) updateRow


    [self.table reloadData];


【讨论】:

不确定你是否需要使用计时器——你可以使用-performSelectorOnMainThread:

以上是关于重新排序单元格后重新加载自定义 UITableViewCell的主要内容,如果未能解决你的问题,请参考以下文章

如何在swift 4中重新排序单元格后保存tableView顺序

重新排列表格视图单元格后如何更新核心数据记录

点击单元格后不同的 UITableViewCell 子视图布局

使用自我调整大小的单元格后,reloadData 不再起作用

使用自定义维度重新排序集合视图单元格时出现问题

CollectionView 中的自定义单元格重新排序行为